CoreText点击事件处理

这篇我们来讲一下点击事件,看了不少博客将图文混排、点击事件等等都放在一篇来讲,这样的话可能对于我这种小白来说有点难消化,我就将几种需求放在几篇细细道来

思路

我们通过之前几篇已经可以直接进行图文混排了,这次我们需要给富文本加上点击的方法,点击回调的时候将对应的字符串、绘制范围、字符串的位置等等信息

还是捋一下思路,我们的切入点还是在绘制的时候将要添加的点击事件通过自定义key值添加到富文本的addAttributes中,但是我们value值传入的是自定义的CoreTextClickModel对象,然后在绘制CTRun的时候识别我们的自定义key值,再将对应的绘制区域转为NSValuekey CoreTextClickModel对象为value放到字典里面方便遍历时候查找,然后在点击的时候使用enumerateKeysAndObjectsUsingBlock方法遍历字典中的key判断点击的区域是否在区域内然后再使用对象中的值和执行存储的Block,大概这样的流程,我们还是看一下代码。

Demo

CoreTextClickModel

这个类是我们处理点击事件回调等方法的模型对象,因为图方便所以和自定义的UIView写在了一起,自己实现的时候可以根据需求而定,这个模型应该不难理解,主要的作用就是在点击的时候做处理的媒介。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
- (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
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

- (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/,万分感谢