超链接Label

#import <UIKit/UIKit.h>

typedef void(^LinkTap)(NSRange range, NSURL* url);

@interface LinkLabel : UIView

@property (nonatomic, copy) NSString* text;

@property (nonatomic, strong) UIColor* textColor;
@property (nonatomic, assign) CGFloat fontSize;

@property (nonatomic, copy) LinkTap tapLink;

- (void)addLinkRange:(NSRange)range withUrl:(NSURL *)url;

@end
#import "LinkLabel.h"
#import <CoreText/CoreText.h>

@interface LinkLabel () {
    
    CTFrameRef _textFrame;
    NSMutableArray* _lineRects;
    NSMutableDictionary* _linkDic;// 存储链接文本及其range位置
    
}

@end

@implementation LinkLabel

- (instancetype)init {
    
    self = [super init];
    if (self) {
        [self commontInit];
    }
    return self;
    
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commontInit];
    }
    return self;
    
}

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    if (self) {
        [self commontInit];
    }
    return self;
    
}

- (void)commontInit {
    
    UIGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(userTapGesture:)];
    [self addGestureRecognizer:tapRecognizer];
    self.userInteractionEnabled = YES;
    
    _lineRects = [NSMutableArray array];
    _linkDic = [NSMutableDictionary dictionary];
    
}

- (void)addLinkRange:(NSRange)range withUrl:(NSURL *)url {
    
    [_linkDic setObject:url forKey:[NSValue valueWithRange:range]];
    
}

// 正则表达式匹配超链接文本
- (void)matchLinkString:(NSString *)string {
    
    NSError *error;
    NSString *regulaStr = @"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regulaStr
                                                                           options:NSRegularExpressionCaseInsensitive
                                                                             error:&error];
    NSArray *arrayOfAllMatches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];
    
    for (NSTextCheckingResult *match in arrayOfAllMatches)
    {
        NSString* linkStrForMatch = [string substringWithRange:match.range];
        
        [_linkDic setObject:[NSURL URLWithString:linkStrForMatch] forKey:[NSValue valueWithRange:match.range]];
        
    }
    
}

- (void)setText:(NSString *)text {
    
    _text = text;
    [self matchLinkString:text];
    
}

- (void)drawRect:(CGRect)rect {
    
    [super drawRect:rect];
    
    NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:_text];
    
    UIFont* font = [UIFont systemFontOfSize:_fontSize];
    
    [attString addAttribute:(id)kCTFontAttributeName value:font range:NSMakeRange(0, attString.length)];
    
    if (_linkDic.count) {
        
        [_linkDic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            
            NSRange range = [key rangeValue];
            [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:range];
            
        }];
        
    }
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 翻转坐标系
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
    
    CTFrameDraw(frame, context);
    
//    CFRelease(frame);
    
    CFArrayRef lines = CTFrameGetLines(frame);
    CFIndex count = CFArrayGetCount(lines);
    
    // 获得每一行的 origin 坐标
    CGPoint origins[count];
    CTFrameGetLineOrigins(frame, CFRangeMake(0,0), origins);
    
    // 翻转坐标系
    CGAffineTransform transform =  CGAffineTransformMakeTranslation(0, self.bounds.size.height);
    transform = CGAffineTransformScale(transform, 1.f, -1.f);
    
    for (int i = 0; i < count; i++) {
        CGPoint linePoint = origins[i];
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        // 获得每一行的 CGRect 信息
        CGRect flippedRect = [self getLineBounds:line point:linePoint];
        CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
        
        [_lineRects addObject:[NSValue valueWithCGRect:rect]];
        
    }
    
    _textFrame = frame;
    
    CFRelease(path);
    CFRelease(framesetter);
    
}

- (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point {
    CGFloat ascent = 0.0f;
    CGFloat descent = 0.0f;
    CGFloat leading = 0.0f;
    //ascent是文本上线,descent是文本下线,leading是文本起始线
    CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
    CGFloat height = ascent + descent;
    return CGRectMake(point.x, point.y - descent, width, height);
}

- (void)userTapGesture:(UIGestureRecognizer *)recognizer {
    
    CGPoint point = [recognizer locationInView:self];
    
    CFArrayRef lines = CTFrameGetLines(_textFrame);
    CFIndex count = CFArrayGetCount(lines);
    
    for (int i = 0; i < count; i ++) {
        
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGRect rect = [_lineRects[i] CGRectValue];
        
        if (CGRectContainsPoint(rect, point)) {
            
            // 将点击的坐标转换成相对于当前行的坐标
            CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), point.y - CGRectGetMinY(rect));
            
            // 获得当前点击坐标对应的字符串偏移
            // 点击坐标最近的光标的最近字符的index,点击字符前半部分会定位到上一个字符的index,点击后半部分才得到正确的index
            CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
            
            CGFloat glyphStart;
            // 根据index获取对应字符据行初始位置距离
            CTLineGetOffsetForStringIndex(line, idx, &glyphStart);
            
            if (relativePoint.x < glyphStart && idx) {
                --idx;
            }
            
            [self matchAndOpenLinkUrl:idx];
            
            break;
            
        }
        
    }
    
}

- (void)matchAndOpenLinkUrl:(CFIndex)index {
    
    [_linkDic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        NSRange range = [key rangeValue];
        if (NSLocationInRange(index, range)) {
            
            NSURL* url = (NSURL *)obj;
            
            if (self.tapLink) {
                self.tapLink(range, url);
            }
            
            *stop = YES;
            
        }
        
    }];
    
}

@end

xib上使用例子:

@property (weak, nonatomic) IBOutlet LinkLabel *linkLabel;
_linkLabel.text = @"陷阵@86之志有死无生http://ddd.edu,一点寒芒先到,随后枪出如龙。纵使敌众我寡,末将亦能万军从中取敌将首级www.baidu.com";
    _linkLabel.fontSize = 17;
    [_linkLabel addLinkRange:NSMakeRange(2, 3) withUrl:[NSURL URLWithString:@"www.google.com"]];
    _linkLabel.tapLink = ^(NSRange range, NSURL* url) {
        NSLog(@"url:%@", url.absoluteString);
    };


转载于:https://my.oschina.net/u/574245/blog/591466

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值