• 自定义 UITextView 关键字高亮与点击检测

    自定义 UITextView 关键字高亮与点击检测

    一种很简单的方法,妙手偶得,可比较容易地处理 Mention、Hashtag 等

    作者:@nixzhu


    我们大概都知道,设置好 UITextView 的 dataDetectorTypes 后,就可自动检测并高亮网络链接、电话号码、地址等功能。而且在 iPhone 6s (Plus) 上,还可以 3D Touch 网络链接,直接就有 pop/peek 的功能。

    若我们要增加对 mention 或 hashtag 的检测,例如 *@nixzhu* hello 里的 @nixzhuwhat a great #weather! 里的 #weather,该怎么办呢?

    你可能会想着去找一个开源库,例如RichTextView或SwiftyText,它们可能支持很多种标记类型的检测,但也许你就只是需要检测 mention 而已。你当然可以研究其实现来自己改写,看一看 TextKit 相关的文档或 session 视频,弄明白 NSTextStorage、NSLayoutManager、NSTextContainer 等与 UITextView 的关系。

    不过今天我发现并实验了一种比较简单的办法,不需要去了解太复杂的东西(当然 TextKit 仍然值得被了解),所以分享在此。

    以字符串 let text = "@nixzhu Do you like Apple? www.apple.com" 为例,我们希望 @nixzhu 高亮且可点击,点击后自然可以执行某种操作。

    首先,UITextView 有 attributedText: NSAttributedString 属性。我们可以利用正则表达式给上面的字符串的 @nixzhu 加上属性,得到一个 NSAttributedString 再设置给 UITextView,这样高亮就很好实现了。代码大概如下:

    1. let text = "@nixzhu Do you like Apple? www.apple.com"
    2. let attributedString = NSMutableAttributedString(string: text)
    3. let textRange = NSMakeRange(0, (text as NSString).length)
    4. let mentionPattern = "@[A-Za-z0-9_]+" // 可能有更好的正则模式,或者更适合你 app 的正则模式
    5. let mentionExpression = try! NSRegularExpression(pattern: mentionPattern, options: NSRegularExpressionOptions())
    6. mentionExpression.enumerateMatchesInString(text, options: NSMatchingOptions(), range: textRange, usingBlock: { result, flags, stop in
    7. if let result = result {
    8. let subString = (self.text as NSString).substringWithRange(result.range)
    9. let attributes: [String: AnyObject] = [
    10. NSLinkAttributeName: subString,
    11. "CustomDetectionType": "Mention",
    12. ]
    13. attributedString.addAttributes(attributes, range: result.range )
    14. }
    15. })
    16. // TOOD: textView.attributedText = attributedString

    这里我们给符合模式的子字符串添加上了 NSLinkAttributeName 属性和一个自定义检测属性。如果你将 UITextView 的 URL 检测打开,你可以看到 @nixzhuwww.apple.com 都有了颜色和下划线。也就是说,iOS 会将有 NSLinkAttributeName 属性的字符串当做链接。

    Mention in TextView

    此时,我们点击 URL 有反应,点击 mention 也有类似效果,只是没有后续操作,因为我们还没做自定义。

    上面提到 iOS 会将有 NSLinkAttributeName 属性的字符串当做链接,因此,我们可以利用 UITextViewDelegate 的一个方法来做处理:

    1. extension MentionDetectableTextView: UITextViewDelegate {
    2. func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
    3. guard let detectionType = self.attributedText.attribute("CustomDetectionType", atIndex: characterRange.location, effectiveRange: nil) as? String where detectionType == "Mention" else {
    4. return true
    5. }
    6. let text = (self.text as NSString).substringWithRange(characterRange)
    7. let username = text.substringFromIndex(text.startIndex.advancedBy(1))
    8. if !username.isEmpty {
    9. tapMentionAction?(username: username)
    10. }
    11. return true
    12. }
    13. }

    先 guard 看看是否是自定义的类型,不然直接返回 true 让系统处理。若能继续,我们就可以通过参数拿到 username 进而触发一个操作。这里的操作是一个可选闭包,可以在其它地方如 UIViewController 里赋值以做进一步处理。

    这样我们就完成了对 mention 的高亮显示和点击检测,其体验和链接一致。而且,原来的链接检测不受影响,默认的 pop/peek 依然有效。

    具体代码请看 Demo:https://github.com/nixzhu/MentionInUITextViewDemo

    当然,若你还要检测 hashtag 等,稍微修改即可。


    欢迎转载,但请一定注明出处! https://github.com/nixzhu/dev-blog