美文网首页不明觉厉iOSiOS图形处理相关iOS 画图
CoreText实现图文混排之点击事件

CoreText实现图文混排之点击事件

作者: 老司机Wicky | 来源:发表于2016-05-17 00:02 被阅读5289次
CoreText实现图文混排之点击事件

系列文章:


2016.12.25补充:最新demo在第三篇文章末尾。点击算法在第三章也有更新。


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

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

上正文。


CoreText做图文混排之点击事件

主要思路

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

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

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

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

-(void)touchesBegan:(NSSet<UITouch *> *)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(_frame, CFRangeMake(0, 0), origins);
    for (int i = 0; i < lines.count; i ++) {
        CTLineRef line = (__bridge CTLineRef)lines[i];
        CFRange range = CTLineGetStringRange(line);
        ranges[i] = range;
    }
    for (int i = 0; i < _length; i ++) {
        long maxLoc;
        int lineNum;
        for (int j = 0; j < lines.count; j ++) {
            CFRange range = ranges[j];
            maxLoc = range.location + range.length - 1;
            if (i <= maxLoc) {
                lineNum = j;
                break;
            }
        }
        CTLineRef line = (__bridge CTLineRef)lines[lineNum];        CGPoint origin = origins[lineNum];
        CGRect CTRunFrame = [self frameForCTRunWithIndex:i CTLine:line origin:origin];
        if ([self isFrame:CTRunFrame containsPoint:location]) {  
            NSLog(@"您点击到了第 %d 个字符,位于第 %d 行,然而他没有响应事件。",i,lineNum + 1);//点击到文字,然而没有响应的处理。可以做其他处理
            return;
        }
    }
    NSLog(@"您没有点击到文字");
}

-(BOOL)isIndex:(NSInteger)index inRange:(NSRange)range
{
    if ((index <= range.location + range.length - 1) && (index >= 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 < runs.count; k ++) {
        CTRunRef run = (__bridge CTRunRef)runs[k];
        CFRange range = CTRunGetStringRange(run);
        NSRange rangeOC = NSMakeRange(range.location, range.length);
        if ([self isIndex:index inRange:rangeOC]) {
            runCurrent = run;
            break;
        }
    }
    CTRunGetTypographicBounds(runCurrent, CFRangeMake(0, 0), &lineAscent, &lineDescent, NULL);
    CGFloat height = lineAscent + lineDescent;
    return CGRectMake(offsetX, offsetY, offsexX2 - offsetX, height);
}

看上去也挺多的,我们还是分段讲解吧。


分段解析

-touchesBegan

之所以把他放在首位,是因为他作为整个view响应点击事件的入口扮演者十分重要的角色。

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

///点击方法
-(void)touchesBegan:(NSSet<UITouch *> *)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(_frame, CFRangeMake(0, 0), origins);//获取所有CTLine的原点
    for (int i = 0; i < lines.count; i ++) {
        CTLineRef line = (__bridge CTLineRef)lines[i];
        CFRange range = CTLineGetStringRange(line);
        ranges[i] = range;
    }//获取所有CTLine的Range
    for (int i = 0; i < _length; i ++) {//逐字检查
        long maxLoc;
        int lineNum;
        for (int j = 0; j < lines.count; j ++) {//获取对应字符所在CTLine的index
            CFRange range = ranges[j];
            maxLoc = range.location + range.length - 1;
            if (i <= maxLoc) {
                lineNum = j;
                break;
            }
        }
        CTLineRef line = (__bridge CTLineRef)lines[lineNum];//取到字符对应的CTLine
        CGPoint origin = origins[lineNum];
        CGRect CTRunFrame = [self frameForCTRunWithIndex:i CTLine:line origin:origin];//计算对应字符的frame
        if ([self isFrame:CTRunFrame containsPoint:location]) {//如果点击位置在字符范围内,响应时间,跳出循环
            NSLog(@"您点击到了第 %d 个字符,位于第 %d 行,然而他没有响应事件。",i,lineNum + 1);//点击到文字,然而没有响应的处理。可以做其他处理
            return;
        }
    }
    NSLog(@"您没有点击到文字");//没有点击到文字,可以做其他处理
}

看上去很多是吧?有没有怕怕的。

仔细看你会发现,有很多代码跟昨天的有相似之处,就是这样,因为这里也遍历了每一个CTRun,只不过更加细化到CTRun中的每个字

NSArray * lines = (NSArray *)CTFrameGetLines(self.data.ctFrame);//获取所有CTLine
    CFRange ranges[lines.count];//初始化范围数组
    CGPoint origins[lines.count];//初始化原点数组
    CTFrameGetLineOrigins(_frame, CFRangeMake(0, 0), origins);//获取所有CTLine的原点

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

for (int i = 0; i < lines.count; i ++) {
        CTLineRef line = (__bridge CTLineRef)lines[i];
        CFRange range = CTLineGetStringRange(line);
        ranges[i] = range;
    }//获取所有CTLine的Range

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

for (int i = 0; i < _length; i ++)

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

for (int j = 0; j < lines.count; j ++) {//获取对应字符所在CTLine的index
            CFRange range = ranges[j];
            maxLoc = range.location + range.length - 1;
            if (i <= maxLoc) {
                lineNum = j;
                break;
            }
        }

这里又是一层循环,通过当前字符序号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 < runs.count; k ++) {//获取当前点击的CTRun
        CTRunRef run = (__bridge CTRunRef)runs[k];
        CFRange range = CTRunGetStringRange(run);
        NSRange rangeOC = NSMakeRange(range.location, range.length);
        if ([self isIndex:index inRange:rangeOC]) {
            runCurrent = run;
            break;
        }
    }
    CTRunGetTypographicBounds(runCurrent, CFRangeMake(0, 0), &lineAscent, &lineDescent, NULL);//计算当前点击的CTRun高度
    offsetY -= lineDescent;
    CGFloat height = lineAscent + lineDescent;
    return CGRectMake(offsetX, offsetY, offsexX2 - offsetX, height);//返回一个字符的Frame
}

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

  • 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 + range.length - 1) && (index >= range.location)) {
        return YES;
    }
    return NO;
}

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

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

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

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

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

最后放一张效果图吧。


大萌神镇楼

呐,了却一桩心事。。。

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

哦,这段代码是我自己的解决方案,所以要转载的同学,一定要注明出处哦,这次是一定哦。貌似你不注明我也拦不住你。。。啧啧啧。。。
http://www.jianshu.com/p/51c47329203e

参考资料:

2016年05月16日23点52分

老司机Wicky

相关文章

网友评论

  • Everdinner:多谢分享,目前看到的关于CoreText最好且讲解最细的文章!
    老司机Wicky:@Everdinner 多谢鼓励
  • 潘鹭瑶:楼主,求demo:grat-ly@qq.com
  • LByy:36357962@qq.com 楼主把这个demo发给我号码 谢谢。。
    老司机Wicky:@LByy 在第三篇文章开头
  • LByy:你这个点击能拿到点击的文字不?15375627569@163.com
    老司机Wicky:@LByy 能,第三篇文章开头
  • 凌海龙:老司机,虽然很多人问了,我还是要问一遍,因为github上面的demo没看太懂
    NSArray * lines = (NSArray *)CTFrameGetLines(self.data.ctFrame);//获取所有CTLine
    CFRange ranges[lines.count];//初始化范围数组
    CGPoint origins[lines.count];//初始化原点数组
    CTFrameGetLineOrigins(_frame, CFRangeMake(0, 0), origins);//获取所有CTLine的原点
    self.data.ctFrame和_frame具体是啥?:joy: :joy:
    凌海龙:@老司机Wicky swrlybz@163.com 谢老司机:smile:
    老司机Wicky:@Tristan_ling 额,那个是第一版demo里面的一个类,当时把计算的地方抽出去在外面写的。如果你想看第一版demo的话,留个邮箱,我发给你
  • 8fca649f491c:辛苦了,我求个demo xdcheng52@163.com
  • 哈哈哈我的简书账号:我不太清楚您的这几句代码哈,您帮我看下
    CTRunGetTypographicBounds(runCurrent, CFRangeMake(0, 0), &lineAscent, &lineDescent, NULL);//计算当前点击的CTRun高度
    offsetY -= lineDescent;您这一句减去这个值,那么换算成系统坐标系的话,不就是下一行的frame嘛
    CGFloat height = lineAscent + lineDescent;
    return CGRectMake(offsetX, offsetY, offsexX2 - offsetX, height);//返回一个字符的Frame
    您算的这句代码CTFrameGetLineOrigins(_frame, CFRangeMake(0, 0), origins);//获取所有CTLine的原点
    您说CTLine的原点,是指哪个呀?是基线的位置还是每个CTline的左上角,还是右下角呢?
  • 594f0d694a3f:写的很好 可不可以求一份Demo研究一下 辛苦了
    老司机Wicky:@哇哇哈哈哈 文章开头写了,demo在第三篇文章
  • iFengx:楼主,求demo:xlqiao@163.com
    老司机Wicky:@iFengx 哦,是末尾
    老司机Wicky:@iFengx 文章开头处有说,demo在第三篇文章开头
  • 9c98a8df41e7:1500多行代码 看完要疯了!
    9c98a8df41e7:@老司机Wicky 希望看到你更精彩文章!:jack_o_lantern::jack_o_lantern::jack_o_lantern:
    9c98a8df41e7:@老司机Wicky 好吧 不过 写的不错! 如果将文件拆分开来读起来会更容易一点!:relaxed::relaxed::relaxed:
    老司机Wicky:@payforyou 最开始没支持pod的时候是想着一对.h.m导入的时候方便些:stuck_out_tongue_winking_eye:
  • 丐帮头:老司机源码能给一份吗?526036901@qq.com,谢谢!
    9c98a8df41e7:1500多行代码! 看完要疯了!
    丐帮头:@老司机Wicky 谢谢
    老司机Wicky:@丐帮头 第三篇开头有demo
  • 九天揽月__:大神求demo!!! ytappledeveloper@163.com
    老司机Wicky:@帅气的小袁 在第三篇尾部
  • JunesYin:老司机,求demo (yinshiling@126.com)
    老司机Wicky:在第三篇里面有demo:stuck_out_tongue_winking_eye:
  • 张三的三E:老司机 求这篇文章demo 搞不懂文中的self.data.ctFrame 1354046565@qq.com
    老司机Wicky:@卡羞羞 看下第三篇文章的demo吧,那是最新版的
  • 雪_晟:膜拜膜拜 ,得好好看大神的每一篇文章
  • RhythmMaster:老司机,写的真详细
  • e25bfb956b88:求demo,大神。1018480690@qq.com
    老司机Wicky:@疯丫头2527 已发送
  • 李捷boyA:老司机,带带我 ,, 1579216424@qq.com
    李捷boyA:@老司机Wicky 谢谢
    老司机Wicky:@哈哈哈哈哈哈啊 已发送
  • King_Whb:老司机,带带我877236396@qq.com
    老司机Wicky:@King_Whb 已发送
  • Double_Chen:大神,我也想要份demo!谢谢!! 602530268@qq.com
    老司机Wicky:@double_chen 已发送,不好意思有点晚了
  • 前进的苏辰:求份Demo研究 ,谢谢. 2647145795@qq.com
    老司机Wicky:@前进的苏辰 已发送
  • 这小子:大神!求份Demo研究! :pray: 925370608@qq.com
    老司机Wicky:@e50d7e470252 已发送
  • 5c16631b5dbf:大神求一份demo :pray: mgv5@qq.com
    老司机Wicky:@指掀涛澜天下惊 已发送
  • initial_J:jia8080@163.com 老司机,求上车
    initial_J:@老司机Wicky 谢谢
    老司机Wicky:已发送
  • caab8338cbe4:尽在不言中,1799854003@qq.com ,请老司机带路
    caab8338cbe4:收到,开车了
    老司机Wicky:@RF乐 已发送,两个是同一个
    caab8338cbe4:@老司机 顺带还有上一篇的Demo。
  • 简简蜗牛:大神,求份demo研究 :grin: 1154569472@qq.com 非常感谢
    594f0d694a3f:写的很好 求Demo 大神 非常感谢 17054214@qq.com
    老司机Wicky:@简简蜗牛 已发送
  • 03a944293b39:老司机,求demo。310155154@qq.com
    老司机Wicky:@一粒含情的沙 已发送
  • 4b9f3d89586c:求demo 1412687739@qq.com 谢谢老司机
    老司机Wicky:@西北风自来 已发送
  • 5c2ad94c9daf:老司机带带我,求demo,邮箱:925839869@qq.com :pray:
    老司机Wicky:@zzy0129 已发送
  • 林安530:老司机 我要上车 求demo~ 2509794848@qq.com
    老司机Wicky:@咦嘻嘻530 已发送
  • 9efe3ac98eed:@老司机Wicky 老司机带带我,求demo 1037226159@qq.com
    9efe3ac98eed:收到 谢谢 :kissing_heart:
    老司机Wicky:@newtouch 已发送
  • efcc0b5be214:老司机求带 :pray: 求demo,1056465980@qq.com
    老司机Wicky:@灵药lmk 已发送
  • 完美的主题曲:@老司机Wicky 申请一份demo源代码谢谢,yaolizhi@aliyun.com
    9efe3ac98eed:收到 感谢 :grin:
    老司机Wicky:@完美的主题曲 已发送
  • 蒲公英yy:老司机求个demo
    diyige163youxiang@163.com 谢谢~~~
    蒲公英yy:@老司机Wicky 不错,注释很多,果然是老司机(つ``ˉ³ˉ)つ``
    老司机Wicky:@蒲公英yy 已发送
  • 4a9efab26352:老司机跪求, :sob: demo 724221147@qq.com
    老司机Wicky:@卡特罗斯 已发送
  • 我的大名叫小爱:老司机 这些方法都是从哪里学到的啊???
    我的大名叫小爱:@老司机Wicky :sunglasses:
    老司机Wicky:@我的大名叫小爱 上一篇有参考资料链接
  • a6ede7bd1a8d:老司机捎我一程呗,求demo 谢谢 robin_guov@163.com
    老司机Wicky:@郭华兵 已发送
  • 9a0600840965:老司机求份demo 研究下呗 谢了changjian2333@163.com
    老司机Wicky:@常见 已发送
  • qinfensky:我要上车!492134993@qq.com
    老司机Wicky:@qinfensky 已发送
  • 吴欧:先赞一个 :+1: :+1: : ,感谢老司机。
    提个小问题哈。返回字符frame的那个方法中,字符的offsetY 不应该只是等于CTLine的origin起点y坐标吧,我觉得还要减去linedescent,有些字符会向下超过基线,那时候刚好点中的话不会触发事件。我用数码测数计的放大效果试了 :grin: :grin:
    CGFloat offsetY = origin.y;
    offsetY -= lineDescent;

    老司机Wicky:@吴欧 说得对,我没考虑到,之前descent给的都是0,所以没注意
  • 辰牧殇:大神!求份Demo研究! :pray: q1015171972@aliyun.com
    枫不停留:老司机 求份demo m13716365887@163.com 万分感谢!!!!!!!!
    九天揽月__:大神求demo!!! ytappledeveloper@163.com
    老司机Wicky:@念归缘 已发送
  • Caolongs:求份Demo研究 :pray: caolongs231@gmail.com
    老司机Wicky:@Caolongs 已发送
  • yourbigbug:果然是老司机牛人,方便给个demo吗?453304118@qq.com
  • yourbigbug:非常不错
  • 一剑寒潇:求发demo,947161577@qq.com,谢谢~
    老司机Wicky:@vision123 已发送
  • lumic000:顶起,跪求demo,583383489@qq.com
    老司机Wicky:@Migi000 已发送
  • ec776a3f9e66:隐隐约约懂了,但是不知道那个self.data.ctFrame是文字的frame?给份Demo吧,继续研究,很详细,谢谢。84144758@qq.com
    老司机Wicky:@linkinzone 已发送
  • d3a0d2d0fdcc:写的好详细。。。。我是新手还是有些不明白的。能给发个demo么(zh_cobra@qq.com)谢谢啦
    老司机Wicky:@d3a0d2d0fdcc 已发送
  • 鼻毛长长:wyqp11@163.com
    老司机,带带我

    鼻毛长长:@老司机Wicky 十分感谢
    老司机Wicky:@鼻毛长长 已发送
  • hopestar90:老司机您好~有源码demo可以分享一下吗~
    老司机Wicky:@hopestar90 已发送
    hopestar90:@老司机Wicky hopestar90@sina.com 谢谢老司机~
    老司机Wicky:@hopestar90 留邮箱啊
  • nuannuan_nuan:感谢楼主的详细讲解以及无私奉献 :smiley:
    老司机Wicky:@nuannuan_nuan 我绘制,圆角,没问题
    nuannuan_nuan:@nuannuan_nuan 昨天尝试写了下,发下了一个图文混排的问题,如果绘制一个circle image,那么在这个circle image的左下角会有个黑色边框的矩形小区域,绘制方形图片不会存在这个问题,在网上也没找到问题根源,亲了解吗 :flushed:
  • 空恋的爱:辛苦啦@求demo
    老司机Wicky:@空恋的爱 已发送
    空恋的爱:@老司机Wicky wangltom@163.com.我想看看这个底层的实现
    老司机Wicky:@空恋的爱 代码都在上面,推荐自己照着敲一遍,另外你连邮箱都不留我怎么给你发demo
  • bd47d2f54ece:干的漂亮,谢谢分享
    老司机Wicky:@牛仔裤uuu 哈哈,谢谢鼓励

本文标题:CoreText实现图文混排之点击事件

本文链接:https://www.haomeiwen.com/subject/gstirttx.html