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

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

作者:@nixzhu


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

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

你可能会想着去找一个开源库,例如RichTextViewSwiftyText,它们可能支持很多种标记类型的检测,但也许你就只是需要检测 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