富文本渲染+点击事件

本编是研究新浪微博首页效果的笔记总结,总的来说需要处理的有两个部分:

  1. 特殊字符的渲染,比如用户名、话题、小图标、url等
  2. 特殊字符的点击事件,需要定位触发点在那段特殊字符中,并以此做不同的处理,如果点击的是非特殊字符,则响应cell的点击事件

接口文件在这里,demo的效果如下:

假设一段文字所包含的内容分为普通子串+4类特殊子串:用户名、图片、url、话题

1
2
3
4
5
6
enum SpecilType {
case username
case pic
case url
case topic
}

从接口解析到这段文字之后,我们需要在模型中进行这段文字处理,包括:按照如上5种类型拆分这段文字、将拆分的子串按类别进行渲染并最终合并成一段新富文本串。
拆分文字需要用到正则表达式,为方便使用,先稍微封装下正则的使用,如下
1
2
3
4
5
6
7
8
9
10
11
12
extension String {

func match(pattern: String) -> [NSTextCheckingResult] {

guard let regular = try? NSRegularExpression(pattern: pattern,
options: []) else {
return []
}
let range = NSMakeRange(0, count)
return regular.matches(in: self, options: [], range: range)
}
}

接下来将使用四个正则表示是来拆分文字中的用户名、图片、url、话题这四部分
1
2
3
4
5
6
7
8
// 匹配用户名
let namePattern = "@[a-zA-Z0-9\\u4e00-\\u9fa5]+"
// 匹配图片
let picPattern = "\\[[\\u4e00-\\u9fa5]+\\]"
// 使用简易的url正则匹配url
let urlPattern = "https://[a-zA-Z0-9/\\.-_]+"
// 匹配话题
let topicPattern = "#[a-aA-Z0-9\\u4e00-\\u9fa5]+#"

我们将这4类子串封装成SpecialText模型,并且使用数组specialTexts来存储这些子串,但是由于拆分之后specialTexts储存的顺序已经不是原文字的顺序,所以我们需要根据SpecailText.range.location进行一次排序。
现在已经有序的拿到了这四种特殊子串,还差普通子串,本该是对上面4中正则的取反匹配就可以了,但是这段正则我一直写不正确,所以换了一种思路。先把原文字和四种特殊子串直接转化成富文本类型,然后使用NSMutableAttributedString.replaceCharacters(in range: NSRange, with attrString: NSAttributedString)并替换掉原文字的富文本相应range就行了,剩下的就是普通文本了。这其中还有一个细节需要留意一下,我们获取的原文字中对应的图片是用几个文字描述的,比如[哈哈哈],转成图片之后就只有一个字符长度,所以我们需要一个变量统计图片字符串转图片所减少的字符长度characterCount,所以我们在遍历处理specialTexts的时候,需要做specialText.rang.location - characterCount操作,这样获取的range的才是正确的。详细可见代码Status.swift
至此已经完成了第一部分内容,特殊字符的渲染,就下来就要完成第二部分特殊文字和普通文字的点击事件。
使用UILabel是无法完成这种功能的,UITextView可以,首先要对UITextView进行基本的设置,如下
1
2
3
4
5
6
7
// 除去上下边距(textContainerInset即使把左右设置为0,还是有左右边距的存在)
textContainerInset = .zero
// 除去左右边距
textContainer.lineFragmentPadding = 0
isEditable = false
isScrollEnabled = false
isSelectable = false

主要使用的UITextView的接口

  1. selectionRects(for range: UITextRange) -> [UITextSelectionRect]返回的是UITextView的选中区域Rect数组
  2. selectedTextRange: UITextRange?**UITextView的选中区域,这个变量不能不能直接设置
  3. selectedRange: NSRange,设置UITextView的选中区域,将影响到属性2

思路就是当发成点击事件,我们遍历specailTexts,分别尝试设置选中区域为特殊子串的区域,即selectedRange = specailText.rane,然后selectedTextRange自动改变为特殊子串的区域,再通过selectionRects获取获取到该特殊子串的Rect数组,如果触点在其中该数组的其中一个Rect中,那么响应事件就是点击了该特殊子串,否则即是点击了其他的普通文字,详细代码在这里
完整demo在这里


插一个小bug

前面设置了UITextView不能滚动,在cell高度自适应的时候,发现最后一行不能显示出来,解决办法是计算的高度+1作为cell的高度。

显示 Gitment 评论