/HowRegexWorks-Swift

用Swift学习正则表达式是如何工作的(笔记+源码)

Primary LanguageSwift

HowRegexWorks-Swift

用Swift学习Regex是如何工作(笔记+源码)

###--文章中用到的符号解释: A --- 表示功能1:实现替换字符或者高亮字符

B --- 表示功能2:实现数据验证,验证比如输入的是否是电话号码或者邮件

C --- 表示功能3:

1/5 --- 5步中的第一步

2/5 --- 5步中的第二步

...


###--A功能:实现替换字符或者高亮字符

####第A:1/5步:点击search弹出设置过滤条件的页面

####第A:2/5步:viewWillAppear 如果已经搜索过,那么默认会显示这些条件

        if let options = searchOptions {
            searchTextField.text = options.searchString
            replacementTextField.text = options.replacementString
            replaceTextSwitch.on = (options.replacementString != nil)
            matchCaseSwitch.on = options.matchCase
            wholeWordsSwitch.on = options.wholeWords
        }

####第A:3/5步:点击Search返回首界面

因为搜索的过滤条件是一个结构体:

struct SearchOptions {
    let searchString: String
    var replacementString: String?
    let matchCase: Bool
    let wholeWords: Bool
}

所以可以很方便设置其内部的全部变量

        searchOptions = SearchOptions(searchString: searchTextField.text,
                                      replacementString: (replaceTextSwitch.on) ? replacementTextField.text : nil,
                                      matchCase: matchCaseSwitch.on,
                                      wholeWords: wholeWordsSwitch.on )

启动返回主页面的Segue

        performSegueWithIdentifier(Storyboard.Identifiers.UnwindSegueIdentifier, sender: self)

####第A:4/5步:从过滤条件页面返回触发unwind segue

    @IBAction func unwindToTextHighlightViewController(segue: UIStoryboardSegue) {
        if let searchOptionsViewController = segue.sourceViewController as? SearchOptionsViewController {
            
            //如果是点击cancel,searchOptionsViewController.searchOptions被设置成了nil,所以不会进入下面这个方法;如果是search,searchOptionsViewController.searchOptions不为空,所以会进入下面这个方法。
            //本来这里有个疑问?既然都返回了,searchOptionsViewController.searchOptions应该已经release掉了才对啊,结果在这里打了个断点发现,这个方法执行时,屏幕依然是显示searchOptionsViewController的,也就是说还没真正返回,所以下面let options = searchOptionsViewController.searchOptions就是赶紧用一个常量保存searchOptions。
            if let options = searchOptionsViewController.searchOptions {
                //进入正式查找的方法
                performSearchWithOptions(options)
            }
        }
    }

####第A:5/5步:替换或者高亮的核心代码

    func performSearchWithOptions(searchOptions: SearchOptions) {
        self.searchOptions = searchOptions
        
        //如果需要替换,进入searchForText方法;如果不需要替换,进入highlightText方法
        if let replacementString = searchOptions.replacementString {
            searchForText(searchOptions.searchString, replaceWith: replacementString, inTextView: textView)
        } else {
            highlightText(searchOptions.searchString, inTextView: textView)
        }
    }

####如果是替换字符

    func searchForText(searchText: String, replaceWith replacementText: String, inTextView textView: UITextView) {
        let beforeText = textView.text
        let range = NSMakeRange(0, count(beforeText))
        
        //*********************************************************
        //********* 在第六步中:调用"RegexHelpers"生成正则表达式 *******
        //*********************************************************        
        if let regex = NSRegularExpression(options: self.searchOptions!){

            //regex就是正则表达式,范围是整个文本,在整个文本里面用"正则表达式"去匹配,返回一个替换之后的文本,再把替换之后的文本重新赋值给textView
            let afterText = regex.stringByReplacingMatchesInString(beforeText, options: NSMatchingOptions.allZeros, range: range, withTemplate: replacementText)
            textView.text  = afterText
        }
    }

####如果是高亮字符

    func highlightText(searchText: String, inTextView textView: UITextView) {

        //可变拷贝一份textView的attributedText
        let attributedText = textView.attributedText.mutableCopy() as! NSMutableAttributedString
        
        //范围还是全文,并把原来上一次的高亮都移除,计算NSMutableAttributedString的长度可以用attributedText.length
        let attributedTextRange = NSMakeRange(0, attributedText.length)
        attributedText.removeAttribute(NSBackgroundColorAttributeName, range: attributedTextRange)

        //*********************************************************
        //********* 在第六步中:调用"RegexHelpers"生成正则表达式 *******
        //*********************************************************
        if let regex = NSRegularExpression(options: self.searchOptions!) {
            //计算textView.text的长度要用 count(textView.text)
            let range = NSMakeRange(0, count(textView.text))

            //这个方法返回一个数组,数组里面是匹配得到的结果。每个结果是AnyObject类型的。所以我们需要转成NSTextCheckingResult这个类型。这个类型中有一个变量可以获取当前结果在全文中的range。
            let matches = regex.matchesInString(textView.text, options: .allZeros, range: range)
            
            //遍历每一个匹配项(把它们转换成NSTextCheckingResult对象),并为每一项添加黄色背景。
            for match in matches as! [NSTextCheckingResult] {
                let matchRange = match.range //这个range是全文中的range
                //每一个match都设置背景色
                attributedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellowColor(), range: matchRange)
            }
        }
        
        textView.attributedText = attributedText.copy() as! NSAttributedString
    }

###--B功能:实现数据验证,验证比如输入的是否是电话号码或者邮件

####首先,请先自行自考下面的正则表达式是什么?

**First name:**应该包含一到十个字符长度的标准英语字母

**Middle initial:**应该包含一个英语字母。

**Last name:**应该包含标准英语字母加上撇号‘(apostrophe)(如这样的名字 O’Brian) 并且二到十个字符长度。

Date of birth: dd/mm/yyyy, dd-mm-yyyy, 或 dd.mm.yyyy, 且要落在 1/1/1900 和 31/12/2099之间。

答案:

"^[a-z]{1,10}$", // First name

"^[a-z]$", // Middle Initial

"^[a-z']{2,10}$", // Last Name

"^(0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\\d\\d$" // Date of Birth