#import
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
@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
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
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
--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);
};