級(jí)別:★☆☆☆☆
標(biāo)簽:「UILabel」「TTTAttributedLabel 基本使用」「TTTAttributedLabel 實(shí)現(xiàn)」
作者: WYW
審校: QiShare團(tuán)隊(duì)
前言:筆者最近需要實(shí)現(xiàn)給 UILabel 中的鏈接添加點(diǎn)擊事件的功能檐薯。使用 so.com 查了下科展,發(fā)現(xiàn) TTTAttributedLabel 的封裝程度比較好还棱。整理了 TTTAttributedLabel 的基本使用判族,及部分實(shí)現(xiàn)。
TTTAttributedLabel 的基本使用
把 TTTAttributedLabel.h
及 TTTAttributedLabel.m
放到項(xiàng)目中
遵守 TTTAttributedLabelDelegate
協(xié)議
// 遵守TTTAttributedLabelDelegate協(xié)議
@interface ViewController () <TTTAttributedLabelDelegate>
創(chuàng)建 TTTAttributedLabel 實(shí)例及相應(yīng)配置
創(chuàng)建 TTTAttributedLabel 實(shí)例足绅,添加相應(yīng)配置。
- (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);
}
實(shí)現(xiàn)TTTAttributedLabelDelegate 代理方法
在如下代理方法中查看當(dāng)前點(diǎn)擊的鏈接。
//! 實(shí)現(xiàn)代理方法
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
NSLog(@"url信息:%@", url);
}
TTTAttributedLabel 部分實(shí)現(xiàn)
設(shè)置 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)";
查看了 TTTAttributedLabel 的大概實(shí)現(xiàn)流程壳贪。
設(shè)置 TTTAttributedLabel 用戶交互可用。
self.userInteractionEnabled = YES;
TTTAttributedLabel 鏈接指定了默認(rèn)鏈接樣式
TTTAttributedLabel 為鏈接指定了默認(rèn)鏈接樣式為藍(lán)色和帶下劃線寝杖。
相關(guān)代碼為:
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
檢測(cè) label.text
中的鏈接
NSArray *results = [dataDetector matchesInString:[(NSAttributedString *)text string] options:0 range:NSMakeRange(0, [(NSAttributedString *)text length])];
NSArray<NSTextCheckingResult *> *
類型的 results
數(shù)組中會(huì)有 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
"自動(dòng)檢測(cè)"鏈接
當(dāng)我們?cè)O(shè)置了 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)";
時(shí),會(huì)發(fā)現(xiàn)https://github.com/mattt/TTTAttributedLabel/
自動(dòng)變?yōu)殒溄有问缴弧W詣?dòng)檢測(cè)出 label.text
中的文本中有 url
信息是依次在 - (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results attributes:(NSDictionary *)attributes
及 - (void)addLinks:(NSArray *)links
的實(shí)現(xiàn)中做的處理磕蒲。
首先通過在
- (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results attributes:(NSDictionary *)attributes
方法中封裝鏈接文本屬性信息媒介TTTAttributedLabelLink
。再通過在
- (void)addLinks:(NSArray *)links
方法中根據(jù)TTTAttributedLabelLink
傳遞過來的文本屬性及url 位置信息收苏,設(shè)置label.attributedText
以達(dá)到能夠自動(dòng)檢測(cè)出label.text
中的url
的目的亿卤。
另外我們自行添加addLinkToURl
- (void)addLinks:(NSArray *)links {
...
self.attributedText = mutableAttributedString;
...
}
添加鏈接到指定 range
以 [attriLabel addLinkToURL:[NSURL URLWithString:@"http://github.com/mattt/"] withRange:range];
為例÷拱裕可以發(fā)現(xiàn) TTTAttributedLabel 內(nèi)部實(shí)現(xiàn)是
[self addLinkWithTextCheckingResult:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]];
直到
[self addLinkWithTextCheckingResult:result attributes:self.linkAttributes];
就和上述的“自動(dòng)檢測(cè)”鏈接的內(nèi)容是一樣的排吴。
點(diǎn)擊Label 的鏈接文字后,查找到對(duì)應(yīng) url
這部分內(nèi)容主要分為 touchesBegan
方法中查找點(diǎn)擊的位置的鏈接懦鼠, touchesMoved
方法中比對(duì)觸摸到Label 上的位置變動(dòng)后钻哩,當(dāng)前位置的鏈接和 touchesBegan
方法中找到的鏈接是否還一樣,最后在 touchesEnded
中把點(diǎn)擊的鏈接以為block 和 代理的方式傳遞出去肛冶。
下邊簡(jiǎn)單說明下筆者查看touchesBegan方法中查找點(diǎn)擊鏈接的內(nèi)容街氢。
- 獲取到當(dāng)前點(diǎn)擊的點(diǎn)
[touch locationInView:self]
- 在
- (TTTAttributedLabelLink *)linkAtPoint:(CGPoint)point
方法中獲取到當(dāng)前點(diǎn)鏈接- 首先在
- (CFIndex)characterIndexAtPoint:(CGPoint)p
方法中找到點(diǎn)擊的位置的字符的索引 - 然后通過
- (TTTAttributedLabelLink *)linkAtCharacterIndex:(CFIndex)idx
方法中找到對(duì)應(yīng)字符的索引的TTTAttributedLabelLink實(shí)例(其中包含當(dāng)前點(diǎn)擊點(diǎn)的鏈接信息)
- 首先在
相應(yīng)代碼,詳情見 TTTAttributedLabel
[self linkAtPoint:[touch locationInView:self]];
TTTAttributedLabelLink *result = [self linkAtCharacterIndex:[self characterIndexAtPoint:point]];
獲取到當(dāng)前點(diǎn)擊位置的字符的索引
筆者感覺其中難理解的地方為獲取到當(dāng)前點(diǎn)擊位置的字符的索引睦袖。相關(guān)內(nèi)容如下:
把當(dāng)前點(diǎn)的坐標(biāo)轉(zhuǎn)換為對(duì)應(yīng) UILabel 中的文字坐標(biāo)轉(zhuǎn)換為針對(duì)于UILabel 自身坐標(biāo)系的點(diǎn)坐標(biāo)珊肃;
p = CGPointMake(p.x - textRect.origin.x, p.y - textRect.origin.y);
另一個(gè)是把iOS 做左上角為原點(diǎn)坐標(biāo)轉(zhuǎn)換為CT 坐標(biāo)系的左下角為原點(diǎn)坐標(biāo)的調(diào)整。
p = CGPointMake(p.x, textRect.size.height - p.y);
- 根據(jù)當(dāng)前l(fā)abel相對(duì)自身坐標(biāo)系 frame 及 屬性字符串 創(chuàng)建CT坐標(biāo)系所需的frame
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL);
- 確定UILabel 當(dāng)前在CT 坐標(biāo)系顯示占用的行數(shù)
CFArrayGetCount(lines)
NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
- 遍歷CT 坐標(biāo)中文字的每一行,找到當(dāng)前點(diǎn)擊點(diǎn)所在的行伦乔,計(jì)算出點(diǎn)擊點(diǎn)相對(duì)于當(dāng)前行的坐標(biāo)厉亏,并計(jì)算出當(dāng)前索引。
CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
idx = CTLineGetStringIndexForPosition(line, relativePoint);
其中還有ascent(字形最高點(diǎn)到baseline的推薦距離) 和descent(字形最低點(diǎn)到baseline的推薦距離) 相關(guān)的內(nèi)容等烈和。筆者不了解爱只,有興趣的話,可以查看CoreText相關(guān)內(nèi)容招刹,如深入理解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 的基本使用及部分實(shí)現(xiàn)恬试。有興趣的讀者請(qǐng)下載TTTAttributedLabel 查看詳情。
參考學(xué)習(xí)網(wǎng)址
推薦文章:
用SwiftUI給視圖添加動(dòng)畫
用SwiftUI寫一個(gè)簡(jiǎn)單頁(yè)面
iOS 控制日志的開關(guān)
iOS App中可拆卸一個(gè)framework的兩種方式
自定義WKWebView顯示內(nèi)容(一)
Swift 5.1 (7) - 閉包
Swift 5.1 (6) - 函數(shù)
Swift 5.1 (5) - 控制流
Xcode11 新建工程中的SceneDelegate
iOS App啟動(dòng)優(yōu)化(二)—— 使用“Time Profiler”工具監(jiān)控App的啟動(dòng)耗時(shí)
iOS App啟動(dòng)優(yōu)化(一)—— 了解App的啟動(dòng)流程