本编是研究新浪微博首页效果的笔记总结,总的来说需要处理的有两个部分:
- 特殊字符的渲染,比如用户名、话题、小图标、url等
- 特殊字符的点击事件,需要定位触发点在那段特殊字符中,并以此做不同的处理,如果点击的是非特殊字符,则响应cell的点击事件
接口文件在这里,demo的效果如下:
假设一段文字所包含的内容分为普通子串+4类特殊子串:用户名、图片、url、话题1
2
3
4
5
6enum SpecilType {
case username
case pic
case url
case topic
}
从接口解析到这段文字之后,我们需要在模型中进行这段文字处理,包括:按照如上5种类型拆分这段文字、将拆分的子串按类别进行渲染并最终合并成一段新富文本串。
拆分文字需要用到正则表达式,为方便使用,先稍微封装下正则的使用,如下1
2
3
4
5
6
7
8
9
10
11
12extension 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的接口
- selectionRects(for range: UITextRange) -> [UITextSelectionRect]返回的是UITextView的选中区域Rect数组
- selectedTextRange: UITextRange?**UITextView的选中区域,这个变量不能不能直接设置
- selectedRange: NSRange,设置UITextView的选中区域,将影响到属性2
思路就是当发成点击事件,我们遍历specailTexts,分别尝试设置选中区域为特殊子串的区域,即selectedRange = specailText.rane,然后selectedTextRange自动改变为特殊子串的区域,再通过selectionRects获取获取到该特殊子串的Rect数组,如果触点在其中该数组的其中一个Rect中,那么响应事件就是点击了该特殊子串,否则即是点击了其他的普通文字,详细代码在这里。
完整demo在这里
前面设置了UITextView不能滚动,在cell高度自适应的时候,发现最后一行不能显示出来,解决办法是计算的高度+1作为cell的高度。