![](http://file.zhishichong.com/images/article/20161028/13e8022cd902b8f264d28984a80c373c.png)
系列文章:
今天呢,我们继续把CoreText图文混排的点击事件
补充上,这样我们的图文混排也算是圆满了。
哦,上一篇的链接在这里
CoreText实现图文混排。所有需要用到的准备知识
都在上一篇,没有赶上车的朋友可以去补个票~
上正文。
CoreText做图文混排之点击事件
主要思路
我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有
-(void)touchesBegan:(NSSet)touches withEvent:(UIEvent )event方法,我们呢,就是基于这个方法去做点击事件的。
1 |
通过touchBegan方法拿到当前点击到的点,然后通过坐标判断这个点是否在某段文字上,如果在则触发对应事件。 |
上面呢就是主要思路。接下来呢,我们来详细讲解一下。还是老规矩,先上代码。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch * touch = [touches anyObject]; CGPoint location = [self systemPointFromScreenPoint:[touch locationInView:self]]; if ([self checkIsClickOnImgWithPoint:location]) { return; } [self ClickOnStrWithPoint:location]; } -(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location { if ([self isFrame:_imgFrm containsPoint:location]) { NSLog(@"您点击到了图片"); return YES; } return NO; } -(void)ClickOnStrWithPoint:(CGPoint)location { NSArray * lines = (NSArray *)CTFrameGetLines(self.data.ctFrame); CFRange ranges[lines.count]; CGPoint origins[lines.count]; CTFrameGetLineOrigins(self.data.ctFrame, CFRangeMake(0, 0), origins); for (int i = 0; i = range.location)) { return YES; } return NO; } -(CGPoint)systemPointFromScreenPoint:(CGPoint)origin { return CGPointMake(origin.x, self.bounds.size.height - origin.y); } -(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point { return CGRectContainsPoint(frame, point); } -(CGRect)frameForCTRunWithIndex:(NSInteger)index CTLine:(CTLineRef)line origin:(CGPoint)origin { CGFloat offsetX = CTLineGetOffsetForStringIndex(line, index, NULL); CGFloat offsexX2 = CTLineGetOffsetForStringIndex(line, index + 1, NULL); offsetX += origin.x; offsexX2 += origin.x; CGFloat offsetY = origin.y; CGFloat lineAscent; CGFloat lineDescent; NSArray * runs = (__bridge NSArray *)CTLineGetGlyphRuns(line); CTRunRef runCurrent; for (int k = 0; k |
看上去也挺多的,我们还是分段讲解吧。
分段解析
-touchesBegan
之所以把他放在首位,是因为他作为整个view响应点击事件的入口
扮演者十分重要的角色。
他负责接收点击事件
,根据条件将点击事件分发给不同的对象
去执行相应的响应。
1 2 3 4 5 6 7 8 9 10 |
///点击方法 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch * touch = [touches anyObject]; CGPoint location = [self systemPointFromScreenPoint:[touch locationInView:self]];//获取点击位置的系统坐标 if ([self checkIsClickOnImgWithPoint:location]) {//检查是否点击在图片上,如果在,优先响应图片事件 return; } [self ClickOnStrWithPoint:location];//响应字符串事件 } |
这里老司机还是要解释一下,为什么我要设置成优先响应图片的事件呢?
是这样的,在我们使用的过程中,大部分的场景是如下过程:
- 给整段富文本添加属性,事件等
- 插入图片
- 给图片设置点击事件
正是因为这样,我们可以看出逻辑上图片的响应事件的优先级明显是要高于文字的。即使是一段文字范围我们赋值了文字的响应事件
,然后在范围中插入了图片并且赋予了图片响应事件,我们往往是希望图片响应其自己的事件
。同时,不知道你们是否还记得上一趟车我们已经求出了图片的frame,如果优先判断出点击的是图片的话将会减少很多计算量
,提高运行效率
。所以我这里将图片的响应优先级定义的高于文字,不过根据需要我们可以定义不同的响应优先级。
搞明白这一点以后,其实逻辑就很简单了。
- 首先呢,先取出当前点击的到屏幕坐标的点。
- 将屏幕坐标转换为系统坐标(不懂得同学快去上一节补课)
- 判断是否点击在图片上
- 如果未点击图片执行点击文字
获取点击坐标
-touchesBegan事件给我们提供了touches这么一个集合。里面装满了UITouch对象。
因为集合是无序的,所以我们通过anyObject取出其中的一个UITouch对象。
UITouch对象的locationInView是专门用来给出UITouch对象在某个View中的坐标的方法,因此我们可以用这个方法来求出当前点击位置的系统坐标。这段比较基础,想画个重点都不知道画哪。
坐标转换
这里用到了第一个工具方法(老司机习惯把写好的方法分类,这些中间方法老司机习惯叫他们工具方法),-(CGPoint)systemPointFromScreenPoint:(CGPoint)origin。
简单的说一句,因为屏幕坐标与系统坐标的不同,我们要将坐标系统一成系统坐标
,这样才能计算,所以才有了这个坐标转换的方法。其实很简单
1 2 3 4 5 6 7 8 |
///坐标转换 /* 将屏幕坐标转换为系统坐标 */ -(CGPoint)systemPointFromScreenPoint:(CGPoint)origin { return CGPointMake(origin.x, self.bounds.size.height - origin.y); } |
上一讲有坐标系的图,这里我就不细讲了。直接进入下一话题。
点击图片判断
第二个工具方法
-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
///图片点击检查 /* 遍历图片frame的数组与点击位置比较,如果在 范围内则响应的数组中取出对应响应并执行,返 回yes,否则返回no */ -(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location { if ([self isFrame:_imgFrm containsPoint:location]) { NSLog(@"您点击到了图片"); return YES; } return NO; } |
这里呢,我们用到了第三个工具方法,顺便就说了吧
-(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point
1 2 3 4 5 |
///点包含检测 -(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point { return CGRectContainsPoint(frame, point); } |
事实上也是调用了系统的一个方法CGRectContainsPoint()
。这个方法两个参数,一个是frame,一个是point。可以返回point是否在frame中。
不过还是有一点需要注意的。由于传入的point是系统坐标
(本例中),所以frame我们一定要传入系统坐标系下的frame
才能正确对应。
这里老司机偷了个懒,直接把上一讲中求得的图片frame改成了一个实例变量,这样在这里的方法中我就能直接调用了。这只是个demo,所以我就怎么方便怎么来了,实际使用中,你可以把frame保存在数组或字典中
。你问我怎么在数组或字典中保存一个frame这样的结构体?恩,有一个系统类叫NSValue
,专门针对这种结构体。
如果-(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point返回YES则说明在图片范围内,则响应图片的点击事件
,
并且-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location返回YES
,否则返回NO。
回到上一层,如果-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location返回YES,则说明点击的是图片并且已经执行完响应事件
,直接return结束方法即可
。否则则继续检查是否点击到了文字。
点击文字判断
终于进入重中之重了,点击文字的逻辑了,不过你也别害怕,如果你对上一讲的讲解有了一定的理解的话,这里将变得简单一些。
![](http://file.zhishichong.com/images/article/20161028/ec24422e12ae103c60c6bd8fc38bd7f9.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
///字符串点击检查 /* 实际上接受所有非图片的点击事件,将字符串的每个 字符取出与点击位置比较,若在范围内则点击到文字 ,进而检测对应的文字是否响应事件,若存在响应 */ -(void)ClickOnStrWithPoint:(CGPoint)location { NSArray * lines = (NSArray *)CTFrameGetLines(self.data.ctFrame);//获取所有CTLine CFRange ranges[lines.count];//初始化范围数组 CGPoint origins[lines.count];//初始化原点数组 CTFrameGetLineOrigins(self.data.ctFrame, CFRangeMake(0, 0), origins);//获取所有CTLine的原点 for (int i = 0; i |
看上去很多是吧?有没有怕怕的。
仔细看你会发现,有很多代码跟昨天的有相似之处,就是这样,因为这里也遍历
了每一个CTRun,只不过更加细化到CTRun中的每个字
。