这篇我们来讲一下点击事件,看了不少博客将图文混排、点击事件等等都放在一篇来讲,这样的话可能对于我这种小白来说有点难消化,我就将几种需求放在几篇细细道来
思路
我们通过之前几篇已经可以直接进行图文混排了,这次我们需要给富文本加上点击的方法,点击回调的时候将对应的字符串、绘制范围、字符串的位置等等信息
还是捋一下思路,我们的切入点还是在绘制的时候将要添加的点击事件通过自定义key
值添加到富文本的addAttributes
中,但是我们value
值传入的是自定义的CoreTextClickModel
对象,然后在绘制CTRun
的时候识别我们的自定义key
值,再将对应的绘制区域转为NSValue
为key
CoreTextClickModel
对象为value
放到字典里面方便遍历时候查找,然后在点击的时候使用enumerateKeysAndObjectsUsingBlock
方法遍历字典中的key
判断点击的区域是否在区域内然后再使用对象中的值和执行存储的Block
,大概这样的流程,我们还是看一下代码。
Demo
CoreTextClickModel
这个类是我们处理点击事件回调等方法的模型对象,因为图方便所以和自定义的UIView
写在了一起,自己实现的时候可以根据需求而定,这个模型应该不难理解,主要的作用就是在点击的时候做处理的媒介。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| NSString *const CoreTextHighLightAttributeName = @"CoreTextHighLightAttributeName";
typedef void(^CoreTextHighLightBlock)(NSDictionary *parameter);
@interface CoreTextClickModel: NSObject
@property (nonatomic, copy) CoreTextHighLightBlock coreTextHighLightBlock; @property (nonatomic, assign) CGRect rect; @property (nonatomic, assign) NSRange range; @property (nonatomic, copy) NSString *string;
@end
@implementation CoreTextClickModel @end
|
计算CTRun
的绘制区域
将之前的绘制图片计算CTRun
绘制区域的方法独立拆了出来用于计算制定的CTRun
的绘制区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| - (CGRect)drawWithRectangle:(CTFrameRef)frame line:(CTLineRef)line run:(CTRunRef)run point:(CGPoint)point{ //距离顶部基线的距离 CGFloat ascent; //距离底部基线的距离 CGFloat descent; //进行计算的中间变量 CGRect bounds; //获取图片的宽和上下基线的距离 bounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); bounds.size.height = ascent + descent; //获取距离行的第一个字的原点的水平距离 CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL); //计算图片原点的x坐标 bounds.origin.x = point.x + xOffset; //计算图片原点的y坐标 bounds.origin.y = point.y - descent; //获取绘制的区域 CGPathRef path = CTFrameGetPath(frame); //获取裁剪区域的区域 CGRect cutRect = CGPathGetBoundingBox(path); //获取图片的绝对布局 CGRect drawBounds = CGRectOffset(bounds, cutRect.origin.x, cutRect.origin.y); }
|
将点击事件添加到富文本中
如果上面的模型看懂的这个应该很好理解,就直接对处理好的富文本字符串做处理即可。
1 2 3 4 5 6 7 8 9 10
| - (void)setHighLight:(NSMutableAttributedString *)att range:(NSRange)range action:(CoreTextHighLightBlock)action{ if (att.length < range.location + range.length) { return; } CoreTextClickModel *model = [CoreTextClickModel new]; model.coreTextHighLightBlock = action; [att addAttribute:CoreTextHighLightAttributeName value:model range:range]; [att addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range]; }
|
点击事件的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| for (int i = 0 ; i < CFArrayGetCount(lines); i++) {
... for (int j = 0; j < runNumber; j++) { CTRunRef run = CFArrayGetValueAtIndex(runs, j); NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run); if (attributes[CoreTextHighLightAttributeName]) { CFRange _range = CTRunGetStringRange(run); NSRange range = NSMakeRange((long)_range.location, (long)_range.length); CGRect rect = [self drawWithRectangle:frame line:line run:run point:point]; CoreTextClickModel *model = attributes[CoreTextHighLightAttributeName]; model.string = [att attributedSubstringFromRange:range].string; model.range = range; model.rect = rect; NSValue *value = [NSValue valueWithCGRect:rect]; self.clickDic[value] = model; } ... } }
|
上面的代码主要是在遍历CTRun
的时候判断是否要添加点击事件对象,然后获取CTRun
的绘制区域作为key
点击事件对象为value
添加到字典中,我们也可以使用经过优化的hashMap
,这里面我们就先使用基本的字典对象。
执行点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; UITouch * touch = [touches anyObject]; CGPoint location = [touch locationInView:self]; [self.clickDic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { NSValue *value = key; CGRect rect = [self convertRect:value.CGRectValue]; if (CGRectContainsPoint(rect, location)) { CoreTextClickModel *model = obj; NSDictionary *parameter = @{ @"string": model.string, @"range": [NSValue valueWithRange:model.range], @"rect":[NSValue valueWithCGRect:model.rect] }; model.coreTextHighLightBlock(parameter); *stop = YES; } }]; }
-(CGRect)convertRect:(CGRect)rect{ return CGRectMake(rect.origin.x, self.bounds.size.height - rect.origin.y - rect.size.height, rect.size.width, rect.size.height); }
|
下面的方法我们可以自己添加到自己的分类里面,因为我们在遍历CTRun
的时候是将系统坐标放到了字典中,我们在点击事件的时候需要转化一下iOS的屏幕坐标来判断点击是否在区域内。在点击时候我们使用enumerateKeysAndObjectsUsingBlock
方法遍历字典中的键值使用,判断转化后的坐标是否在遍历出的key
值转换的区域内,然后取出遍历的对象来执行block
,然后返回对象的各个参数。
这种方法没有做具体的优化,可能在点击对象多的时候遍历处理的时间相对长,这个就需要依据需求来做优化了。
Demo
英文水平贼渣的我又看了好久英文文档和博客总结的,如果转载请附上链接https://coderwong.com/2018/07/10/CoreTextTouch/,万分感谢