IOS CoreText系列四:图文混排之点击事件

基于-(void)touchesBegan:(NSSet<UITouch *> )touches withEvent:(UIEvent )event这个方法拿到当前点击到的点,然后通过坐标判断这个点是否在某段文字上,如果在则触发对应事件。

1、首先需要执行touchesBegan这个方法来做相应的判断

[objc]  view plain  copy
  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  
  2. {  
  3.     UITouch *touch = [touches anyObject];  
  4.     CGPoint location = [self systemPointFromScreenPoint:[touch locationInView:self]];  
  5.     //判断点击的是图片还是文字  
  6.     if ([self checkIsClickOnImgWithPoint:location]) {  
  7.         return;  
  8.     }  
  9.     [self clickOnStringWithPoint:location];  
  10. }  

2、点击的是图片做相应的处理,图片的位置我们在图文混排的时候会获取到,这边直接拿过来用,如果图片有很多张的话,需要搞个循环来做判断。

[objc]  view plain  copy
  1. #pragma mark 点击图片  
  2. - (BOOL)checkIsClickOnImgWithPoint:(CGPoint)location  
  3. {  
  4.     if ([self isFrame:_imgFrm containsPoint:location]) {  
  5.         NSLog(@"你点击到了图片");  
  6.         return YES;  
  7.     }  
  8.     return NO;  
  9. }  
  10.   
  11. - (BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point  
  12. {  
  13.     return CGRectContainsPoint(frame, point);  
  14. }  
3、点击的是字符串,步骤如下:

[objc]  view plain  copy
  1. /* 
  2.  点击文字判断的大致步骤: 
  3.  1.根据Frame拿到所有Line 
  4.  2.计算每个Line中在全文的range 
  5.  3.计算每个字对应line原点的X值 
  6.  4.比对对应line的origin求得字对应起点坐标 
  7.  5.求得下一个字的横坐标和上一行的origin,结合起点坐标得出字的坐标范围 
  8.  6.屏幕坐标与drawRect坐标转换,判断是否在范围内 
  9.  */  
[objc]  view plain  copy
  1. #pragma mark 点击字符串  
  2. - (void)clickOnStringWithPoint:(CGPoint)point  
  3. {  
  4.     //获取所有CTLine  
  5.     NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrm);  
  6.     //初始化范围数组  
  7.     CFRange ranges[lines.count];  
  8.     //初始化原点数组  
  9.     CGPoint origins[lines.count];  
  10.     //获取所有CTLine的原点  
  11.     CTFrameGetLineOrigins(self.ctFrm, CFRangeMake(00), origins);  
  12.       
  13.     //获取每个CTLine中包含的富文本在整串富文本中的范围。将所有CTLine中字符串的范围保存下来放入数组中。  
  14.     for (int i = 0; i < lines.count; i ++) {  
  15.         CTLineRef line = (__bridge CTLineRef)(lines[i]);  
  16.         CFRange range = CTLineGetStringRange(line);  
  17.         ranges[i] = range;  
  18.     }  
  19.       
  20.     for (int i = 0;i < self.strLength; i ++) {  
  21.         long maxLoc;  
  22.         int lineNum;  
  23.         for (int j = 0; j < lines.count; j ++) {  
  24.             CFRange range = ranges[j];  
  25.             maxLoc = range.location + range.length - 1;  
  26.             if (i <= maxLoc) {  
  27.                 lineNum = j;  
  28.                 break;  
  29.             }  
  30.         }  
  31.           
  32.         CTLineRef line = (__bridge CTLineRef)(lines[lineNum]);  
  33.         CGPoint origin = origins[lineNum];  
  34.         CGRect ctRunFrame = [self frameForCTRunWithIndex:i ctLine:line origin:origin];  
  35.         if ([self isFrame:ctRunFrame containsPoint:point]) {  
  36.             NSLog(@"您点击到了第 %d 个字符,位于第 %d 行,然而他没有响应事件。",i,lineNum + 1);  
  37.               
  38.             return;  
  39.         }  
  40.     }  
  41.     NSLog(@"您没有点击到文字");  
  42. }  
  43.   
  44. /** 
  45.  字符Frame计算 
  46.  
  47.  @param index  索引 
  48.  @param line   索引字符所在CTLine 
  49.  @param origin line的起点 
  50.  
  51.  @return 返回Frame 
  52.  */  
  53. - (CGRect)frameForCTRunWithIndex:(NSInteger)index ctLine:(CTLineRef)line origin:(CGPoint)origin  
  54. {  
  55.     //获取字符起点相对于CTLine的原点的偏移量  
  56.     CGFloat offsetX = CTLineGetOffsetForStringIndex(line, index, NULL);  
  57.     //获取下一个字符的偏移量,两者之间即为字符X范围  
  58.     CGFloat offsetX2 = CTLineGetOffsetForStringIndex(line, index + 1NULL);  
  59.     offsetX += origin.x;  
  60.     offsetX2 += origin.x;  
  61.     CGFloat offsetY = origin.y;  
  62.     CGFloat lineAscent,lineDescent;  
  63.     //获取当前点击的CTRun  
  64.     NSArray *runs = (NSArray *)CTLineGetGlyphRuns(line);  
  65.     CTRunRef runCurrent;  
  66.     for (int k = 0; k < runs.count; k ++) {  
  67.         CTRunRef run = (__bridge CTRunRef)(runs[k]);  
  68.         CFRange range = CTRunGetStringRange(run);  
  69.         NSRange rangeOC = NSMakeRange(range.location, range.length);  
  70.         if ([self isIndex:index inRange:rangeOC]) {  
  71.             runCurrent = run;  
  72.             break;  
  73.         }  
  74.     }  
  75.     //获得对应CTRun的尺寸信息  
  76.     CTRunGetTypographicBounds(runCurrent, CFRangeMake(00), &lineAscent, &lineDescent, NULL);  
  77.     //计算当前点击的CTRun高度  
  78.     CGFloat height = lineAscent + lineDescent;  
  79.     return CGRectMake(offsetX, offsetY, offsetX2 - offsetX, height);  
  80. }  
  81.   
  82.   
  83. /** 
  84.  范围检测 
  85.  
  86.  @param index 索引 
  87.  @param range 范围 
  88.  
  89.  @return 范围内返回yes,否则返回no 
  90.  */  
  91. - (BOOL)isIndex:(NSInteger)index inRange:(NSRange)range  
  92. {  
  93.     if ((index <= (range.location + range.length - 1)) && (index >= range.location)) {  
  94.         return YES;  
  95.     }  
  96.     return NO;  
  97. }  

完整代码如下:

.h文件

[objc]  view plain  copy
  1. #import <UIKit/UIKit.h>  
  2.   
  3. @interface HSCoreTextView : UIView  
  4.   
  5. @end  

.m文件

[objc]  view plain  copy
  1. #import "HSCoreTextView.h"  
  2. #import <CoreText/CoreText.h>  
  3.   
  4. @interface HSCoreTextView ()  
  5.   
  6. @property (nonatomic,assign) CGRect imgFrm;  
  7.   
  8. @property (nonatomic,assign) CTFrameRef ctFrm;  
  9.   
  10. @property (nonatomic,assign) NSInteger strLength;  
  11.   
  12. @end  
  13.   
  14. @implementation HSCoreTextView  
  15.   
  16.   
  17. //图片回调函数  
  18. static CGFloat ascentCallBacks(voidvoid *ref)  
  19. {  
  20.     //__bridge既是C的结构体转换成OC对象时需要的一个修饰词  
  21.     return [[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];  
  22. }  
  23.   
  24. static CGFloat descentCallBacks(voidvoid *ref)  
  25. {  
  26.     return 0;  
  27. }  
  28.   
  29. static CGFloat widthCallBacks(voidvoid *ref)  
  30. {  
  31.     return [[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];  
  32. }  
  33.   
  34. - (void)drawRect:(CGRect)rect  
  35. {  
  36.     [super drawRect:rect];  
  37.       
  38.     //1.绘制上下文  
  39.     //1.1获取绘制上下文  
  40.     CGContextRef context = UIGraphicsGetCurrentContext();  
  41.     //1.2.coreText 起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。若不进行坐标转换,则文字从下开始,还是倒着的,因此需要设置以下属性  
  42.     设置字形的变换矩阵为不做图形变换  
  43.     CGContextSetTextMatrix(context, CGAffineTransformIdentity);  
  44.     //平移方法,将画布向上平移一个屏幕高  
  45.     CGContextTranslateCTM(context, 0self.bounds.size.height);  
  46.     //缩放方法,x轴缩放系数为1,则不变,y轴缩放系数为-1,则相当于以x轴为轴旋转180度  
  47.     CGContextScaleCTM(context, 1.0, -1.0);  
  48.       
  49.     //2.设置图片回调函数  
  50.     //2.1创建一个回调结构体,设置相关参数  
  51.     CTRunDelegateCallbacks callBacks;  
  52.     //memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化  
  53.     memset(&callBacks, 0sizeof(CTRunDelegateCallbacks));  
  54.     //2.2设置回调版本,默认这个  
  55.     callBacks.version = kCTRunDelegateVersion1;  
  56.     //2.3设置图片顶部距离基线的距离  
  57.     callBacks.getAscent = ascentCallBacks;  
  58.     //2.4设置图片底部距离基线的距离  
  59.     callBacks.getDescent = descentCallBacks;  
  60.     //2.5设置图片宽度  
  61.     callBacks.getWidth = widthCallBacks;  
  62.     //2.6创建一个代理  
  63.     NSDictionary *dicPic = @{@"height":@"60",@"width":@"60"};  
  64.     CTRunDelegateRef delegate = CTRunDelegateCreate(&callBacks, (__bridge voidvoid * _Nullable)(dicPic));  
  65.   
  66.     //3.插入图片  
  67.     //创建空白字符  
  68.     unichar placeHolder = 0xFFFC;  
  69.     NSString *placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];  
  70.     NSMutableAttributedString *placeHolderMabString = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];  
  71.     //给字符串中的范围中字符串设置代理  
  72.     CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderMabString, CFRangeMake(01), kCTRunDelegateAttributeName, delegate);  
  73.       
  74.     //4.设置要显示的文字  
  75.     NSMutableAttributedString *mabString = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是富文本"];  
  76.     [mabString insertAttributedString:placeHolderMabString atIndex:12];  
  77.       
  78.     //5.绘制文本  
  79.     //5.1.创建CTFramesetterRef  
  80.     CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabString);  
  81.       
  82.     //5.2.创建路径  
  83.     CGMutablePathRef path = CGPathCreateMutable();  
  84.     CGPathAddRect(path, NULLself.bounds);  
  85.       
  86.     //5.3.创建CTFrame  
  87.     NSInteger length = mabString.length;  
  88.     self.strLength = length;  
  89.     CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, length), path, NULL);  
  90.     self.ctFrm = frame;  
  91.     CTFrameDraw(frame, context);  
  92.       
  93.     //6.添加图片并绘制  
  94.     UIImage *image = [UIImage imageNamed:@"icon-60"];  
  95.     CGRect imgFrm = [self calculateImageRectWithFrame:frame];  
  96.     self.imgFrm = imgFrm;  
  97.     CGContextDrawImage(context, imgFrm, image.CGImage);  
  98.       
  99.     //7.释放  
  100.     CFRelease(delegate);  
  101. //    CFRelease(frame);  
  102.     CFRelease(path);  
  103.     CFRelease(framesetter);  
  104. }  
  105.   
  106. #pragma mark 计算图片Frame  
  107. - (CGRect)calculateImageRectWithFrame:(CTFrameRef)frame  
  108. {  
  109.     //根据frame获取需要绘制的线的数组  
  110.     NSArray *arrLines = (NSArray *)CTFrameGetLines(frame);  
  111.     NSInteger count = arrLines.count;  
  112.     CGPoint points[count];  
  113.     //获取起始点位置  
  114.     CTFrameGetLineOrigins(frame, CFRangeMake(00), points);  
  115.       
  116.     for (int i = 0; i < count; i ++) {  
  117.         CTLineRef line = (__bridge CTLineRef)(arrLines[i]);  
  118.         //CTRun 或者叫做 Glyph Run,是一组共享想相同attributes(属性)的字形的集合体  
  119.         NSArray *arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);  
  120.         for (int j = 0; j < arrGlyphRun.count; j ++) {  
  121.             CTRunRef run = (__bridge CTRunRef)(arrGlyphRun[j]);  
  122.             //获取CTRun的属性  
  123.             NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);  
  124.             CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];  
  125.             if (delegate == nil) {  
  126.                 continue;  
  127.             }  
  128.               
  129.             NSDictionary *dic = CTRunDelegateGetRefCon(delegate);  
  130.             if (![dic isKindOfClass:[NSDictionary class]]) {  
  131.                 continue;  
  132.             }  
  133.               
  134.             //获取一个起点  
  135.             CGPoint point = points[i];  
  136.             //获取上下距  
  137.             CGFloat ascent,desecent;  
  138.             //创建一个Frame  
  139.             CGRect boundsRun;  
  140.             boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(00), &ascent, &desecent, NULL);  
  141.             boundsRun.size.height = ascent + desecent;  
  142.             //获取偏移量  
  143.             CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).locationNULL);  
  144.             boundsRun.origin.x = point.x + xOffset;  
  145.             boundsRun.origin.y = point.y - desecent;  
  146.             //获取绘制路径  
  147.             CGPathRef path = CTFrameGetPath(frame);  
  148.             //获取剪裁区域边框  
  149.             CGRect colRect = CGPathGetBoundingBox(path);  
  150.             CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);  
  151.               
  152.             return imageBounds;  
  153.         }  
  154.     }  
  155.     return CGRectZero;  
  156. }  
  157.   
  158. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  
  159. {  
  160.     UITouch *touch = [touches anyObject];  
  161.     CGPoint location = [self systemPointFromScreenPoint:[touch locationInView:self]];  
  162.     //判断点击的是图片还是文字  
  163.     if ([self checkIsClickOnImgWithPoint:location]) {  
  164.         return;  
  165.     }  
  166.     [self clickOnStringWithPoint:location];  
  167. }  
  168.   
  169. #pragma mark 转化成屏幕坐标  
  170. - (CGPoint)systemPointFromScreenPoint:(CGPoint)origin  
  171. {  
  172.     return CGPointMake(origin.xself.bounds.size.height - origin.y);  
  173. }  
  174.   
  175. #pragma mark 点击图片  
  176. - (BOOL)checkIsClickOnImgWithPoint:(CGPoint)location  
  177. {  
  178.     if ([self isFrame:_imgFrm containsPoint:location]) {  
  179.         NSLog(@"你点击到了图片");  
  180.         return YES;  
  181.     }  
  182.     return NO;  
  183. }  
  184.   
  185. - (BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point  
  186. {  
  187.     return CGRectContainsPoint(frame, point);  
  188. }  
  189.   
  190. /* 
  191.  点击文字判断的大致步骤: 
  192.  1.根据Frame拿到所有Line 
  193.  2.计算每个Line中在全文的range 
  194.  3.计算每个字对应line原点的X值 
  195.  4.比对对应line的origin求得字对应起点坐标 
  196.  5.求得下一个字的横坐标和上一行的origin,结合起点坐标得出字的坐标范围 
  197.  6.屏幕坐标与drawRect坐标转换,判断是否在范围内 
  198.  */  
  199. #pragma mark 点击字符串  
  200. - (void)clickOnStringWithPoint:(CGPoint)point  
  201. {  
  202.     //获取所有CTLine  
  203.     NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrm);  
  204.     //初始化范围数组  
  205.     CFRange ranges[lines.count];  
  206.     //初始化原点数组  
  207.     CGPoint origins[lines.count];  
  208.     //获取所有CTLine的原点  
  209.     CTFrameGetLineOrigins(self.ctFrm, CFRangeMake(00), origins);  
  210.       
  211.     //获取每个CTLine中包含的富文本在整串富文本中的范围。将所有CTLine中字符串的范围保存下来放入数组中。  
  212.     for (int i = 0; i < lines.count; i ++) {  
  213.         CTLineRef line = (__bridge CTLineRef)(lines[i]);  
  214.         CFRange range = CTLineGetStringRange(line);  
  215.         ranges[i] = range;  
  216.     }  
  217.       
  218.     for (int i = 0;i < self.strLength; i ++) {  
  219.         long maxLoc;  
  220.         int lineNum;  
  221.         for (int j = 0; j < lines.count; j ++) {  
  222.             CFRange range = ranges[j];  
  223.             maxLoc = range.location + range.length - 1;  
  224.             if (i <= maxLoc) {  
  225.                 lineNum = j;  
  226.                 break;  
  227.             }  
  228.         }  
  229.           
  230.         CTLineRef line = (__bridge CTLineRef)(lines[lineNum]);  
  231.         CGPoint origin = origins[lineNum];  
  232.         CGRect ctRunFrame = [self frameForCTRunWithIndex:i ctLine:line origin:origin];  
  233.         if ([self isFrame:ctRunFrame containsPoint:point]) {  
  234.             NSLog(@"您点击到了第 %d 个字符,位于第 %d 行,然而他没有响应事件。",i,lineNum + 1);  
  235.               
  236.             return;  
  237.         }  
  238.     }  
  239.     NSLog(@"您没有点击到文字");  
  240. }  
  241.   
  242. /** 
  243.  字符Frame计算 
  244.  
  245.  @param index  索引 
  246.  @param line   索引字符所在CTLine 
  247.  @param origin line的起点 
  248.  
  249.  @return 返回Frame 
  250.  */  
  251. - (CGRect)frameForCTRunWithIndex:(NSInteger)index ctLine:(CTLineRef)line origin:(CGPoint)origin  
  252. {  
  253.     //获取字符起点相对于CTLine的原点的偏移量  
  254.     CGFloat offsetX = CTLineGetOffsetForStringIndex(line, index, NULL);  
  255.     //获取下一个字符的偏移量,两者之间即为字符X范围  
  256.     CGFloat offsetX2 = CTLineGetOffsetForStringIndex(line, index + 1NULL);  
  257.     offsetX += origin.x;  
  258.     offsetX2 += origin.x;  
  259.     CGFloat offsetY = origin.y;  
  260.     CGFloat lineAscent,lineDescent;  
  261.     //获取当前点击的CTRun  
  262.     NSArray *runs = (NSArray *)CTLineGetGlyphRuns(line);  
  263.     CTRunRef runCurrent;  
  264.     for (int k = 0; k < runs.count; k ++) {  
  265.         CTRunRef run = (__bridge CTRunRef)(runs[k]);  
  266.         CFRange range = CTRunGetStringRange(run);  
  267.         NSRange rangeOC = NSMakeRange(range.location, range.length);  
  268.         if ([self isIndex:index inRange:rangeOC]) {  
  269.             runCurrent = run;  
  270.             break;  
  271.         }  
  272.     }  
  273.     //获得对应CTRun的尺寸信息  
  274.     CTRunGetTypographicBounds(runCurrent, CFRangeMake(00), &lineAscent, &lineDescent, NULL);  
  275.     //计算当前点击的CTRun高度  
  276.     CGFloat height = lineAscent + lineDescent;  
  277.     return CGRectMake(offsetX, offsetY, offsetX2 - offsetX, height);  
  278. }  
  279.   
  280.   
  281. /** 
  282.  范围检测 
  283.  
  284.  @param index 索引 
  285.  @param range 范围 
  286.  
  287.  @return 范围内返回yes,否则返回no 
  288.  */  
  289. - (BOOL)isIndex:(NSInteger)index inRange:(NSRange)range  
  290. {  
  291.     if ((index <= (range.location + range.length - 1)) && (index >= range.location)) {  
  292.         return YES;  
  293.     }  
  294.     return NO;  
  295. }  
  296.   
  297. - (void)dealloc  
  298. {  
  299.     CFRelease(_ctFrm);  
  300. }  
  301.   
  302. @end  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值