需求
给出一段多行文本,要求给前面部分文字添加圆角边框(下面简称这个为标签),要求能够控制标签和文字之间的距离。
最初方案
由于 AttributeString 自带的加边框的属性不支持圆角,加之开发时间比较紧,于是采用了:
UILabel->UIImage->NSTextAttachment->AttributeString
+ (UIImage *)imageWithLabel:(UILabel *)label {
UIGraphicsBeginImageContextWithOptions(label.frame.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
[label.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
(NSTextAttachment + AttributeString) = FinalAttributeString 传给 UILabel ,交给 UILabel 去进行展示。
测试了一个极限情况:让标签文本很多
产生的问题:
tableView 去计算高度的时候消耗了大量的时间。
计算高度伪代码:
new UILabel
UILabel.text = FinalAttributeString
UILabel sizeToFit
return UILabel.height
总结:imageWithLabel 会导致大量的时间的浪费
方案优化
使用 CoreText 去绘制文本,计算文本排版,获取需要绘制矩形的区域。
使用 Core Graphics 对矩形区域进行圆角边框的绘制
大致过程
get AttributeString
CTFrameSetter = CreateCTFrameSetter(AttributeString)
CTFrame = CTFrameSetter(CGPath)
// 处理 CTFrame -> CTLine -> CTRun
origins[](基准线坐标)= GetOrigns(CTFrame)
Draw(CTFrame)/Draw(CTLine)/Draw(CTRun)
类设计
- XXLabel:UIView
- XXLayout
- AttributeString(XXBorder)
AttributeString(XXBorder)
扩展 AttributeString,可以添加边框的宽度、颜色等属性,到时候需要提交给 XXLabel。
XXLabel
模拟 UILabel 的属性,接收 AttributeString ,根据 attribute 绘制文字和边框
XXLayout
传入 AttributeString ,可以获取需要展示的高度。(主要在 TableView 算高 和 [XXLabel sizeToFit] 的时候使用)
主要绘制伪代码:
- (void)drawRect:(CGRect)rect {
CTFrameSetter = CreateCTFrameSetter(AttributeString)
CTFrame = CTFrameSetter(CGPath)
origins[](基准线坐标)= GetOrigns(CTFrame)
Rects[]// 保存了边框的矩形
for (origin in origins) {
Temp Rect //
CTRuns = GetRuns(CTLine)
for (CTRun in CTRuns) {
attribute = GetAttributes(CTRun)
if (attribute 有边框属性){
记录 CTRun 的 Rect。
根据边框属性确定是否需要和 Temp Rect 合并
把 Rect 保存到 Rects 中
}
}
}
Draw(CTFrame)
}
导致的问题
很难控制标签和正文之间的距离
很难实现标签文本和正文实现水平居中
解决方案
- 在生成 AttributeString 的时候就通过插入空格(@” 标签文本 “)去实现控制标签和正文的距离
- 在生成 AttributeString 中的标签文本前后分别插入 attachment 实现占位(attachment 被换行了的话会导致 bug )
- 根据 attribute 和标签的 CTRun 的坐标计算新坐标,一个个 CTRun 进行重新绘制。
总结
- 根据边框属性去计算需要绘制 CTRun 的位置,然后对其进行特殊处理,获取后面的 CTRun 的基准线,使得两者水平居中
- 需要大量特殊判断,不利于扩展
目前方案
用 CoreText 绘制文字,标签使用一个 UIView ,贴在 View 中
类设计
- AtrtributedStringProcesser : NSObject
- AttributeStringAttachment : NSObject
- RichTextDrawer : NSObject
- RichTextLabel : UIView
- RichTextLayout : NSObject
流程:
- 生成 Attachment ,表示需要占位的 Width,Ascend,Descend.
- 生成 AtrtributedString, 文本作为 Identifier(到时候根据 Identifier 会去问 delegate 索取需要插入的 View) ,把 Attachment 当做 attribute 插入。
- 把 AtrtributedString 传给 RichTextLabel
- RichTextLabel 通过 AtrtributedStringProcesser 把 AtrtributedString 中的作为 Identifier 的文本过滤成一个字符(不能是空格回车之类的,这里会有坑)
- RichTextLabel 通知 RichTextDrawer 去绘制 AtrtributedString
- RichTextDrawer 处理 AtrtributedString ,根据 Identifier 去向 delegate 索取需要传入的 View,完成绘制。
总结
- 只绘制文本,预留出空间给 View 进行展示
- 绘制效率高
v1.5.2