今天呢,我们继续把CoreText图文混排的点击事件补充上,这样我们的图文混排也算是圆满了。

哦,上一篇的链接在这里

http://www.jianshu.com/p/6db3289fb05d

CoreText实现图文混排。所有需要用到的准备知识都在上一篇,没有赶上车的朋友可以去补个票~

上正文。


CoreText做图文混排之点击事件

主要思路

我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有

-(void)touchesBegan:(NSSet)touches withEvent:(UIEvent )event方法,我们呢,就是基于这个方法去做点击事件的。

通过touchBegan方法拿到当前点击到的点,然后通过坐标判断这个点是否在某段文字上,如果在则触发对应事件。

上面呢就是主要思路。接下来呢,我们来详细讲解一下。还是老规矩,先上代码。

-(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响应点击事件的入口扮演者十分重要的角色。

他负责接收点击事件,根据条件将点击事件分发给不同的对象去执行相应的响应。

///点击方法

-(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。

简单的说一句,因为屏幕坐标与系统坐标的不同,我们要将坐标系统一成系统坐标,这样才能计算,所以才有了这个坐标转换的方法。其实很简单

///坐标转换

/*

将屏幕坐标转换为系统坐标

*/

-(CGPoint)systemPointFromScreenPoint:(CGPoint)origin

{

return CGPointMake(origin.x, self.bounds.size.height - origin.y);

}

上一讲有坐标系的图,这里我就不细讲了。直接进入下一话题。


点击图片判断

第二个工具方法

-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location

///图片点击检查

/*

遍历图片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

///点包含检测

-(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结束方法即可。否则则继续检查是否点击到了文字。


点击文字判断

终于进入重中之重了,点击文字的逻辑了,不过你也别害怕,如果你对上一讲的讲解有了一定的理解的话,这里将变得简单一些。

逻辑图

///字符串点击检查

/*

实际上接受所有非图片的点击事件,将字符串的每个

字符取出与点击位置比较,若在范围内则点击到文字

,进而检测对应的文字是否响应事件,若存在响应

*/

-(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中的每个字。

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的原点

这四句我就不多说了,获取所有CTLine和其原点。

for (int i = 0; i

获取每个CTLine中包含的富文本在整串富文本中的范围。将所有CTLine中字符串的范围保存下来放入数组备用。

for (int i = 0; i

这个for循环用来遍历富文本中的每一个字符。下面的代码都是在for循环中的循环体。

for (int j = 0; j

这里又是一层循环,通过当前字符序号i与每个CTLine包含字符的范围比较来求得当前计算的是哪个CTLine中的字符。

CTLineRef line = (__bridge CTLineRef)lines[lineNum];//取到字符对应的CTLine

CGPoint origin = origins[lineNum];

CGRect CTRunFrame = [self frameForCTRunWithIndex:i CTLine:line origin:origin];//计算对应字符的frame

取得当前字符所在的CTLine并取得该CTLine的原点,同时通过这里的第五个工具方法

-(CGRect)frameForCTRunWithIndex:(NSInteger)index

CTLine:(CTLineRef)line

origin:(CGPoint)origin

计算当前字符的frame。

分解讲一下这个方法

///字符frame计算

/*

返回索引字符的frame

index:索引

line:索引字符所在CTLine

origin:line的起点

*/

-(CGRect)frameForCTRunWithIndex:(NSInteger)index

CTLine:(CTLineRef)line

origin:(CGPoint)origin

{

CGFloat offsetX = CTLineGetOffsetForStringIndex(line, index, NULL);//获取字符起点相对于CTLine的原点的偏移量

CGFloat offsexX2 = CTLineGetOffsetForStringIndex(line, index + 1, NULL);//获取下一个字符的偏移量,两者之间即为字符X范围

offsetX += origin.x;

offsexX2 += origin.x;//坐标转换,将点的CTLine坐标转换至系统坐标

CGFloat offsetY = origin.y;//取到CTLine的起点Y

CGFloat lineAscent;//初始化上下边距的变量

CGFloat lineDescent;

NSArray * runs = (__bridge NSArray *)CTLineGetGlyphRuns(line);//获取所有CTRun

CTRunRef runCurrent;

for (int k = 0; k

根据注释就能很轻易的看懂这段代码,不过可能有几个方法不熟悉,我来介绍下。

  • CTLineGetOffsetForStringIndex(,,)

获取一行文字中,指定charIndex字符相对x原点的偏移量,返回值与第三个参数同为一个值。如果charIndex超出一行的字符长度则反回最大长度结束位置的偏移量,如一行文字共有17个字符,哪么返回的是第18个字符的起始偏移,即第17个偏移+第17个字符占有的宽度=第18个起始位置的偏移。因此想求一行字符所占的像素长度时,就可以使用此函数,将charIndex设置为大于字符长度即可。

因为求得的坐标是相对于CTLine原点的偏移量,因此我们要加上CTLine原点的x坐标获得该点的绝对坐标。

  • CTLineGetGlyphRuns()昨天有介绍过,拿到CTLine中的所有CTRun。

  • CTRunGetStringRange()获得CTRun在富文本中的范围

  • CTRunGetTypographicBounds(,,,,)获得对应CTRun的尺寸信息

中间用了第六个工具方法

-(BOOL)isIndex:(NSInteger)index inRange:(NSRange)range

///范围检测

/*

范围内返回yes,否则返回no

*/

-(BOOL)isIndex:(NSInteger)index inRange:(NSRange)range

{

if ((index = range.location)) {

return YES;

}

return NO;

}

这个代码很简单我就不多说了。

通过以上方法,你就拿到了每一个字符的frame了。

可以返回至上一层了=。=喘了一口气。。。

接受到字符的frame,还是判断点击位置是否在frame中,如果在,则响应点击事件并结束方法。如果没有不在任何一个字符的frame内,则说明没有点击到文字,执行相应的点击事件。

大工告成,到了这里,CoreText做图文混排的点击事件也算是完成了。

最后放一张效果图吧。


呐,了却一桩心事。。。

你要是喜欢呢,麻烦你动一动你可爱的小手点击一下喜欢或者关注,毕竟老司机这么爱慕虚荣的人,而且老司机会经常更新的。

哦,这段代码是我自己的解决方案,所以要转载的同学,一定要注明出处哦,这次是一定哦。貌似你不注明我也拦不住你。。。啧啧啧。。。

http://www.jianshu.com/p/51c47329203e

CoreText实现图文混排之点击事件的更多相关文章

  1. CoreText实现图文混排之点击事件-b

    CoreText实现图文混排之点击事件 主要思路 我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有 -(void)touchesBegan:(NSSet<UIT ...

  2. CoreText实现图文混排之文字环绕及点击算法

    系列文章: CoreText实现图文混排:http://www.jianshu.com/p/6db3289fb05d CoreText实现图文混排之点击事件:http://www.jianshu.co ...

  3. 【iOS】使用CoreText实现图文混排

    iOS没有现成的支持图文混排的控件,而要用多个基础控件组合拼成图文混排这样复杂的排版,是件很苦逼的事情.对此的解决方案有使用CoreText进行绘制,或者使用TextKit.本文主要讲解对于CoreT ...

  4. CoreText 实现图文混排

    CoreText 实现图文混排 相关博文推荐 IOS CoreText.framework - 基本用法 IOS CoreText.framework - 段落样子CTParagraphStyle h ...

  5. CoreText实现图文混排

    CoreText的介绍 Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一种能够对文本格式和文本布局进行精细控制的文本引擎.它良好的结合了 UIKit 和 Core Graph ...

  6. Coretext实现图文混排及Gif图片播放

    CoreText是iOS3.2推出的一套文字排版和渲染框架,可以实现图文混排,富文本显示等效果. CoreText中的几个重要的概念:  CTFont CTFontCollection CTFontD ...

  7. TextView + Spanned实现图文混排以及图片点击交互

    最近要实现图文混排的需求,webview过大,所以想到了用SpannableStringBuilder来实现. 不过参考了大量国内文章,大多数是教你如何实现图文混排,并没有提及图片点击交互的.有翻阅了 ...

  8. CoreText 图文混排

    基本原理 https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html CoreText(一):基本用法  https://blog.csdn ...

  9. iOS 图文混排 链接 可点击

    对于这个话题 我想到 1 第一个解决方法就是使用 webView 比较经典 把所有复杂工作都交给控件本身去处理了,  但是好像好多需要自定义的地方 没法从 webView获得响应回调 :(估计也可以实 ...

随机推荐

  1. Spring 整合Redis 出现 afterPropertiesSet signature: ()V) Incompatible argument to function 解决办法

    正在做SpringMVC+Redis整合的练习 使用的是 spring-data-redis 和 Jedis 配置好之后出现了以下错误: Caused by: java.lang.VerifyErro ...

  2. YII框架的部署 通过YII脚手架程序创建应用程序系统

    1,把YII框架里面的framework复制粘贴到nginx目录下 2,创建一个商城系统: 1)修改环境变量 制定php.exe的目录 2)C:\Users\Administrator>cd C ...

  3. mapreduce (四) MapReduce实现Grep+sort

    1.txt dong xi cheng xi dong cheng wo ai beijing tian an men qiche dong dong dong 2.txt dong xi cheng ...

  4. java Active Object模式(上)

    Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...

  5. mysql数据库操作语句大全

    一 . 常用mysql命令行命令 1 .启动MYSQL服务  net start mysql 停止MYSQL服务  net stop mysql 2 . netstat –na | findstr 3 ...

  6. COJ 2110 Day7-例3

    Day7-例3 难度级别:C: 运行时间限制:5000ms: 运行空间限制:256000KB: 代码长度限制:2000000B 试题描述 输入 输入的第一行包含整数n和k,其中n(2 ≤ n ≤100 ...

  7. ♫【JS】offsetParent

    This property will return null on Webkit if the element is hidden (the style.display of this element ...

  8. 数据结构(堆):SCOI 2009 生日礼物

    Description 小西有一条很长的彩带,彩带上挂着各式各样的彩珠.已知彩 珠有N个,分为K种.简单的说,可以将彩带考虑为x轴,每一个彩珠有一个对应的坐标(即位置).某些坐标上可以没有彩珠,但多个 ...

  9. 图论(无向图的割顶):POJ 1144 Network

    Network   Description A Telephone Line Company (TLC) is establishing a new telephone cable network. ...

  10. Delphi 客户端调用Webservice 的TClientdataset 报出“http://www.borland.com/namespaces/Types-IAppServerSOAP”

    http://www.borland.com/namespaces/Types-IAppServerSOAP 服务器未能识别 HTTP 头 SOAPAction 的值 (2011-04-25 16:4 ...