美文网首页QiShare文章汇总
iOS 给UILabel添加点击事件

iOS 给UILabel添加点击事件

作者: QiShare | 来源:发表于2019-11-28 18:15 被阅读0次

    级别:★☆☆☆☆
    标签:「UILabel」「TTTAttributedLabel 基本使用」「TTTAttributedLabel 实现」
    作者: WYW
    审校: QiShare团队


    前言:笔者最近需要实现给 UILabel 中的链接添加点击事件的功能。使用 so.com 查了下,发现 TTTAttributedLabel 的封装程度比较好。整理了 TTTAttributedLabel 的基本使用,及部分实现。

    TTTAttributedLabel 的基本使用

    TTTAttributedLabel.hTTTAttributedLabel.m 放到项目中

    遵守 TTTAttributedLabelDelegate 协议

    // 遵守TTTAttributedLabelDelegate协议
    @interface ViewController () <TTTAttributedLabelDelegate>
    

    创建 TTTAttributedLabel 实例及相应配置

    创建 TTTAttributedLabel 实例,添加相应配置。

    - (void)setupTTTAttributedLabel {
        
        TTTAttributedLabel *attriLabel = [[TTTAttributedLabel alloc] initWithFrame:CGRectZero];
        attriLabel.font = [UIFont systemFontOfSize:32.0];
        attriLabel.numberOfLines = 0;
        // Automatically detect links when the label text is subsequently changed
        attriLabel.enabledTextCheckingTypes = NSTextCheckingTypeLink;
        // Delegate methods are called when the user taps on a link (see `TTTAttributedLabelDelegate` protocol)
        attriLabel.delegate = self;
        // Repository URL will be automatically detected and linked
        attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)";
        NSRange range = [attriLabel.text rangeOfString:@"me"];
        // Embedding a custom link in a substring
        [attriLabel addLinkToURL:[NSURL URLWithString:@"http://github.com/mattt/"] withRange:range];
        [self.view addSubview:attriLabel];
        attriLabel.frame = CGRectMake(20.0, 100.0, 300.0, 200.0);
    }
    

    实现TTTAttributedLabelDelegate 代理方法

    在如下代理方法中查看当前点击的链接。

    //! 实现代理方法
    - (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
        
        NSLog(@"url信息:%@", url);
    }
    

    TTTAttributedLabel 部分实现

    设置 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)"; 查看了 TTTAttributedLabel 的大概实现流程。

    设置 TTTAttributedLabel 用户交互可用。

    self.userInteractionEnabled = YES;
    

    TTTAttributedLabel 链接指定了默认链接样式

    TTTAttributedLabel 为链接指定了默认链接样式为蓝色和带下划线。

    相关代码为:

    NSMutableDictionary *mutableLinkAttributes = [NSMutableDictionary dictionary];
    [mutableLinkAttributes setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCTUnderlineStyleAttributeName];
    if ([NSMutableParagraphStyle class]) {
        [mutableLinkAttributes setObject:[UIColor blueColor] forKey:(NSString *)kCTForegroundColorAttributeName];
    } else {
        [mutableLinkAttributes setObject:(__bridge id)[[UIColor blueColor] CGColor] forKey:(NSString *)kCTForegroundColorAttributeName];
    }
    self.linkAttributes = [NSDictionary dictionaryWithDictionary:mutableLinkAttributes];
    

    使用 NSDataDetector 检测 label.text 中的链接

    NSArray *results = [dataDetector matchesInString:[(NSAttributedString *)text string] options:0 range:NSMakeRange(0, [(NSAttributedString *)text length])];
    

    NSArray<NSTextCheckingResult *> * 类型的 results 数组中会有 label 中文本的链接信息。

    <__NSArrayM 0x2830a6310>(
    <NSLinkCheckingResult: 0x283ed27c0>{20, 44}{https://github.com/mattt/TTTAttributedLabel/}
    )
    
    // label.text中的URL
    (lldb) po ((NSLinkCheckingResult *)[results lastObject]).URL
    https://github.com/mattt/TTTAttributedLabel/
    
    // label.text中的URL 的range
    (lldb) po ((NSLinkCheckingResult *)[results lastObject]).range
    location=20, length=44
    

    "自动检测"链接

    当我们设置了 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)"; 时,会发现https://github.com/mattt/TTTAttributedLabel/ 自动变为链接形式。自动检测出 label.text 中的文本中有 url 信息是依次在 - (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results attributes:(NSDictionary *)attributes- (void)addLinks:(NSArray *)links 的实现中做的处理。

    • 首先通过在- (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results attributes:(NSDictionary *)attributes 方法中封装链接文本属性信息媒介 TTTAttributedLabelLink

    • 再通过在- (void)addLinks:(NSArray *)links 方法中根据 TTTAttributedLabelLink 传递过来的文本属性及url 位置信息,设置 label.attributedText 以达到能够自动检测出 label.text 中的 url 的目的。

    另外我们自行添加addLinkToURl

    - (void)addLinks:(NSArray *)links {
        ...
        self.attributedText = mutableAttributedString;
        ...
    }
    

    添加链接到指定 range

    [attriLabel addLinkToURL:[NSURL URLWithString:@"http://github.com/mattt/"] withRange:range]; 为例。可以发现 TTTAttributedLabel 内部实现是

    [self addLinkWithTextCheckingResult:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]]; 直到

    [self addLinkWithTextCheckingResult:result attributes:self.linkAttributes]; 就和上述的“自动检测”链接的内容是一样的。

    点击Label 的链接文字后,查找到对应 url

    这部分内容主要分为 touchesBegan 方法中查找点击的位置的链接, touchesMoved 方法中比对触摸到Label 上的位置变动后,当前位置的链接和 touchesBegan 方法中找到的链接是否还一样,最后在 touchesEnded 中把点击的链接以为block 和 代理的方式传递出去。

    下边简单说明下笔者查看touchesBegan方法中查找点击链接的内容。

    • 获取到当前点击的点
    [touch locationInView:self]
    
    • - (TTTAttributedLabelLink *)linkAtPoint:(CGPoint)point 方法中获取到当前点链接
      • 首先在 - (CFIndex)characterIndexAtPoint:(CGPoint)p 方法中找到点击的位置的字符的索引
      • 然后通过 - (TTTAttributedLabelLink *)linkAtCharacterIndex:(CFIndex)idx 方法中找到对应字符的索引的TTTAttributedLabelLink实例(其中包含当前点击点的链接信息)

    相应代码,详情见 TTTAttributedLabel

    [self linkAtPoint:[touch locationInView:self]];
    TTTAttributedLabelLink *result = [self linkAtCharacterIndex:[self characterIndexAtPoint:point]];
    

    获取到当前点击位置的字符的索引

    笔者感觉其中难理解的地方为获取到当前点击位置的字符的索引。相关内容如下:

    把当前点的坐标转换为对应 UILabel 中的文字坐标转换为针对于UILabel 自身坐标系的点坐标;

    p = CGPointMake(p.x - textRect.origin.x, p.y - textRect.origin.y);
    

    另一个是把iOS 做左上角为原点坐标转换为CT 坐标系的左下角为原点坐标的调整。

    p = CGPointMake(p.x, textRect.size.height - p.y);
    
    • 根据当前label相对自身坐标系 frame 及 属性字符串 创建CT坐标系所需的frame
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, textRect);
        CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL);
    
    • 确定UILabel 当前在CT 坐标系显示占用的行数 CFArrayGetCount(lines)
    NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
    
    • 遍历CT 坐标中文字的每一行,找到当前点击点所在的行,计算出点击点相对于当前行的坐标,并计算出当前索引。
    CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
    idx = CTLineGetStringIndexForPosition(line, relativePoint);
    

    其中还有ascent(字形最高点到baseline的推荐距离) 和descent(字形最低点到baseline的推荐距离) 相关的内容等。笔者不了解,有兴趣的话,可以查看CoreText相关内容,如深入理解Core Text排版引擎

        CFIndex idx = NSNotFound;   
    
        CGPoint lineOrigins[numberOfLines];
        CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
    
        for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
            CGPoint lineOrigin = lineOrigins[lineIndex];
            CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
    
            // Get bounding information of line
            CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
            CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
            CGFloat yMin = (CGFloat)floor(lineOrigin.y - descent);
            CGFloat yMax = (CGFloat)ceil(lineOrigin.y + ascent);
    
            // Apply penOffset using flushFactor for horizontal alignment to set lineOrigin since this is the horizontal offset from drawFramesetter
            CGFloat flushFactor = TTTFlushFactorForTextAlignment(self.textAlignment);
            CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, textRect.size.width);
            lineOrigin.x = penOffset;
    
            // Check if we've already passed the line
            if (p.y > yMax) {
                break;
            }
            // Check if the point is within this line vertically
            if (p.y >= yMin) {
                // Check if the point is within this line horizontally
                if (p.x >= lineOrigin.x && p.x <= lineOrigin.x + width) {
                    // Convert CT coordinates to line-relative coordinates
                    CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
                    idx = CTLineGetStringIndexForPosition(line, relativePoint);
                    break;
                }
            }
        }
    

    本文说明了TTTAttributedLabel 的基本使用及部分实现。有兴趣的读者请下载TTTAttributedLabel 查看详情。

    参考学习网址


    推荐文章:
    用SwiftUI给视图添加动画
    用SwiftUI写一个简单页面
    iOS 控制日志的开关
    iOS App中可拆卸一个framework的两种方式
    自定义WKWebView显示内容(一)
    Swift 5.1 (7) - 闭包
    Swift 5.1 (6) - 函数
    Swift 5.1 (5) - 控制流
    Xcode11 新建工程中的SceneDelegate
    iOS App启动优化(二)—— 使用“Time Profiler”工具监控App的启动耗时
    iOS App启动优化(一)—— 了解App的启动流程

    相关文章

      网友评论

        本文标题:iOS 给UILabel添加点击事件

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