级别:★☆☆☆☆
标签:「UILabel」「TTTAttributedLabel 基本使用」「TTTAttributedLabel 实现」
做者: WYW
审校: QiShare团队php
前言:笔者最近须要实现给 UILabel 中的连接添加点击事件的功能。使用 so.com 查了下,发现 TTTAttributedLabel 的封装程度比较好。整理了 TTTAttributedLabel 的基本使用,及部分实现。git
TTTAttributedLabel.h
及 TTTAttributedLabel.m
放到项目中TTTAttributedLabelDelegate
协议// 遵照TTTAttributedLabelDelegate协议
@interface ViewController () <TTTAttributedLabelDelegate>
复制代码
建立 TTTAttributedLabel 实例,添加相应配置。github
- (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);
}
复制代码
在以下代理方法中查看当前点击的连接。数组
//! 实现代理方法
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
NSLog(@"url信息:%@", url);
}
复制代码
设置 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)";
查看了 TTTAttributedLabel 的大概实现流程。bash
self.userInteractionEnabled = YES;
复制代码
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];
就和上述的“自动检测”连接的内容是同样的。
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);
复制代码
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL);
复制代码
CFArrayGetCount(lines)
NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
复制代码
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 查看详情。
小编微信:可加并拉入《QiShare技术交流群》。
关注咱们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)
推荐文章:
用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的启动流程
奇舞周刊