/// 点击的文本类型
enumTapRichTextType:Int{
casenone =0 // 没有点击
caseuser =1 // 点击了用户
caselink =2 // 点击了链接
casetopic =3 // 点击了话题
}
classRichLabel:UILabel{
overridevarattributedText:NSAttributedString? {
didSet{
// 把文本设置为可变的
letattributedString =NSMutableAttributedString(attributedString:attributedText!)
// 添加属性
attributedString.addAttribute(.font, value:font, range:NSRange(location:0, length: attributedString.length))
// 设置 textStorage 的文本内容
textStorage.setAttributedString(attributedString)
// 匹配用户名
userRanges=ranges(from:"@.*?:")
_=userRanges.map{textStorage.addAttribute(.foregroundColor, value:UIColor.blueFontColor(), range: $0) }
// 匹配话题
topicRanges=ranges(from:"#.*?#")
_=topicRanges.map{textStorage.addAttribute(.foregroundColor, value:UIColor.blueFontColor(), range: $0) }
// 匹配链接
linkRanges=rangesOfLink()
_=linkRanges.map{textStorage.addAttribute(.foregroundColor, value:UIColor.blueFontColor(), range: $0) }
}
}
overridefuncdrawText(in rect:CGRect) {
// 绘制字形,设置需要绘制的范围
letrange =NSRange(location:0, length:textStorage.length)
layoutManager.drawGlyphs(forGlyphRange: range, at: .zero)
}
overrideinit(frame:CGRect) {
super.init(frame: frame)
// 设置文本
setupText()
}
requiredinit?(coder aDecoder:NSCoder) {
super.init(coder: aDecoder)
// 设置文本
setupText()
}
/// 文本容器,文本可以排版的区域,默认是矩形,可以自定义区域大小
privatelazyvartextContainer =NSTextContainer()
/// 布局管理者,负责对文字进行编辑排版处理,将存储在 NSTextStorage 中的数据转换为可以在视图控件中显示的文本内容
/// 并把字符编码映射到对应的字形上,然后将字形排版到 NSTextContainer 定义的区域中。
privatelazyvarlayoutManager =NSLayoutManager()
/// NSMutableAttributeString 的子类,主要用来存储文本的字符和相关属性
/// 当 NSTextStorage 中的字符或属性发生改变时,会通知 NSLayoutManager,进而做到文本内容的显示更新。
privatelazyvartextStorage =NSTextStorage()
/// 记录用户名的范围
privatelazyvaruserRanges = [NSRange]()
/// 记录链接的范围
privatelazyvarlinkRanges = [NSRange]()
/// 记录话题的范围
privatelazyvartopicRanges = [NSRange]()
/// 定义一个闭包,点击回调
typealiasTapRichText = (String,NSRange) -> ()
varuserTapped:TapRichText?
varlinkTapped:TapRichText?
vartopicTapped:TapRichText?
/// 点击的类型
privatevartapRichTextType:TapRichTextType= .none
/// 记录用户点击的 range
varselectedRange =NSRange()
}
extension RichLabel {
/// 设置文本
privatefuncsetupText() {
// 将 layoutManager 添加到 textStorage 中
layoutManager.addTextContainer(textContainer)
// 将 textContainer 添加到 layoutManager 中
textStorage.addLayoutManager(layoutManager)
// 设置可以与用户交互
isUserInteractionEnabled = true
// 间距设置 0
textContainer.lineFragmentPadding = 0.0
}
overridefunclayoutSubviews() {
super.layoutSubviews()
// 设置容器的大小为 当前 label 的尺寸
textContainer.size = frame.size
}
funcrangesOfLink() -> [NSRange] {
// 检测正则表达式,NSDataDetector 是 NSRegularExpression 的子类
let regex = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
returnresults(from: regex)
}
/// 返回正则表达式匹配的结果范围
funcranges(from pattern:String) -> [NSRange] {
// 创建正则表达式对象
letregex =try!NSRegularExpression(pattern: pattern, options: [])
returnresults(from: regex)
}
/// 返回正则表达式的结果
privatefuncresults(from regex:NSRegularExpression) -> [NSRange] {
// 开始匹配,返回结果
letcheckingResults = regex.matches(in:textStorage.string, options: [], range:NSRange(location:0, length:textStorage.length))
returncheckingResults.map({ $0.range})
}
/// 根据点击的坐标,获取范围
privatefuncrange(of point:CGPoint) ->NSRange{
// 判断 textStorage 长度是不是 0
iftextStorage.length==0{returnNSRange() }
// 在 textStorage 中的索引
letindex =layoutManager.glyphIndex(for: point, in:textContainer)
// 获取用户名的范围
foruserRangeinuserRanges{
ifindex > userRange.location&& index < userRange.location+ userRange.length{
tapRichTextType= .user
returnuserRange
}
}
// 获取链接的范围
forlinkRangeinlinkRanges{
ifindex > linkRange.location&& index < linkRange.location+ linkRange.length{
tapRichTextType= .link
returnlinkRange
}
}
// 获取话题的范围
fortopicRangeintopicRanges{
ifindex > topicRange.location&& index < topicRange.location+ topicRange.length{
tapRichTextType= .topic
returntopicRange
}
}
returnNSRange()
}
/// 从 range 数组里返回一个 range
/// 暂未使用
funcrange(from ranges: [NSRange], index:Int) ->NSRange{
// 获取话题的范围
foriteminranges {
ifindex > item.location&& index < item.location+ item.length{returnitem }
}
returnNSRange()
}
}
extension RichLabel {
overridefunctouchesBegan(_touches:Set, with event:UIEvent?) {
// 获取点击的范围
selectedRange=range(of: touches.first!.location(in:self))
// 获取点击的范围的内容
letcontent = (textStorage.stringasNSString).substring(with:selectedRange)
// 判断点击的类型
switch tapRichTextType {
case.user:
userTapped?(content,selectedRange)
case.link:
linkTapped?(content,selectedRange)
case.topic:
topicTapped?(content, selectedRange)
default:
break
}
}
}
网友评论