CJLabel富文本三 —— UILabel支持选择复制以及实现原理

2018-02-27 11:12:52来源:https://www.jianshu.com/p/7de3e6d19e31作者:lele8446人点击

分享


CJLabel经过若干版本迭代,各个功能已经日趋完善,并且不断精细,特别是在V4.0.0版本迎来了重头戏:新增enableCopy属性,支持选择、全选、复制功能,类似UITextView的选择复制效果。
老规矩,上效果图:




CJLabel
CJLabel链点点击实现细节

先来回顾一下CJLabel在显示文本以及响应链点点击的过程中,底层是怎样实现的。





CJLabel.png
一. 设置Attributes属性

首先设置需要显示的NSAttributedString文本的属性,除了可设置系统提供的NSFontAttributeName NSForegroundColorAttributeName NSParagraphStyleAttributeName等默认属性外,还支持CJLabel的若干自定义扩展属性:
kCJBackgroundFillColorAttributeName背景填充颜色
kCJBackgroundStrokeColorAttributeName背景边框线颜色
kCJBackgroundLineWidthAttributeName背景边框线宽度
kCJStrikethroughColorAttributeName删除线颜色
……
CJLabel提供配置管理类CJLabelConfigure,专门用来方便设置指定字符的副文本属性,同时还提供了对应的API,调用可生成封装好的NSAttributedString副文本(此处只选取若干方法说明,更多可查看源码


/**
根据图片名初始化NSAttributedString
@param image 图片名称,或者UIImage
@param size 图片大小(这里是指显示图片等区域大小)
@param lineAlignment 图片所在行,图片与文字在垂直方向的对齐方式(只针对当前行)
@param configure 链点配置
@return NSAttributedString
*/
+ (NSMutableAttributedString *)initWithImage:(id)image
imageSize:(CGSize)size
imagelineAlignment:(CJLabelVerticalAlignment)lineAlignment
configure:(CJLabelConfigure *)configure;
/**
根据NSString初始化NSAttributedString
*/
+ (NSMutableAttributedString *)initWithString:(NSString *)string configure:(CJLabelConfigure *)configure;

二. 计算label的CGRect大小


label.attributedText = @"text"



UILabel绘制显示文本,首先会触发以下方法
-textRectForBounds:limitedToNumberOfLines:
-sizeThatFits:
我们可以在这两个方法里面根据需要显示的文本内容以及扩展属性self.textInsets(绘制文本的内边距,默认UIEdgeInsetsZero),计算当前label的CGRect大小,计算使用的核心函数是:


CGSize CTFramesetterSuggestFrameSizeWithConstraints(
CTFramesetterRef framesetter,
CFRange stringRange,
CFDictionaryRef __nullable frameAttributes,
CGSize constraints,
CFRange * __nullable fitRange )

三. CTFrameRef

-drawTextInRect:是真正进行内容绘制的方法,我们将在这里得到所有字符对应的CTFrameRef、CTLineRef以及CTRunRef




CTFrameRef.png

如图,UILabel显示的时候,所有内容都由CTFrameRef管理,然后每一行内容是一个CTLineRef,而每一行CTLineRef中包含了若干个CTRunRef。每一个CTRunRef对应的可能只是一个字符,也可能是整一行文字(连续的具有相同Attributes属性的字符会包含在同一个CTRunRef中),比如以下例子,CTLineRef中包含三个CTRunRef,分别对应为:这是 一段 测试数据三部分。




.jpg

获取到各个字符对应的CTRunRef后,我们可以根据CTRunRef进一步判断得到这一部分字符在UIlabel中对应的CGRect大小,以及在第一步中对当前字符设置的其他自定义属性。这一步也是整体流程中最复杂的部分,涉及到各种坐标数值的转换判断,最后将CTRunRef对应的信息转换为model记录保存。
四. CTRunDraw

上一步已经获取得到了每一个CTRunRef的详细信息,此时我们可以执行最后的图文绘制操作了。首先是绘制自定义背景颜色(即kCJBackgroundFillColorAttributeName相关属性);然后是绘制图文,如果是文字执行CTRunDraw(CTRunRef run, CGContextRef context, CFRange range )函数,如果是图片执行CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image)函数;最后填充边框线以及删除线(kCJBackgroundStrokeColorAttributeName kCJStrikethroughColorAttributeName)。


至此,CJLabel已经完成了显示部分的所有操作。


五. 点击响应

CJLabel默认userInteractionEnabled = YES,如此我们可以在touch相关的方法中捕获到CJLabel的点击事件,通过判断点击触摸点CGPoint是否在保存记录的CGRect数组内,如果是则执行对应点击字符的点击回调事件,同时触发点击字符的高亮重绘(如果存在高亮状态的话,而且CGContextRef的重绘是全局重绘,无法做到局部刷新)。




touches.png

另外给CJLabel添加长按手势UILongPressGestureRecognizer监听,在长按事件中同样执行与touchesBegan:类似的逻辑判断,从而使CJLabel具备长按点击功能。

--------------------------------- 分割线 ---------------------------------


以上便是CJLabel功能的实现原理讲解,下面进入本文的重点——如何使UILabel具备选择复制的能力
当然这里说的选择复制不可能是指点击唤起UIMenuController菜单,然后出现复制剪切选项,点击只能复制所有文本那样的功能。那样的例子网上已经有很多,没有必要在这里再大费周章地来罗列说明。
CJLabel需要具备的是类似于UITextView或UIWebView那样,双击或长按,可出现选择、全选、拷贝选项,同时选中字符左右出现标示大头针,拖动则有放大镜提示当前选中字符,并且尽量做到与系统行为一致。




CJLabel.gif

刚开始面对如此需求的时候,感觉有点无从下手。查遍资料也没找到UITextView或UIWebView中有关选择复制功能的资料说明,更不要说相关的API调用了,很明显苹果并没有将此类功能封装成模块化,就算有那相关的方法也是私有API。
因此在初始的时候,由于开发时间紧,本人选择了使用UITextView代替CJLabel作为显示控件(产品业务要求支持图文混排,支持富文本显示,文本能够自动识别@用户,能够自动识别网址链接并替换为规定的图标展示,文本内容还要支持选择复制……类似于微博的列表页面,但却比它更复杂

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台