iOS 給UILabel添加點(diǎn)擊事件

級(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.hTTTAttributedLabel.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)流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疯暑,一起剝皮案震驚了整個(gè)濱河市训柴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妇拯,老刑警劉巖畦粮,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乖阵,居然都是意外死亡宣赔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門瞪浸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儒将,“玉大人,你說我怎么就攤上這事对蒲」澄茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蹈矮,是天一觀的道長(zhǎng)砰逻。 經(jīng)常有香客問我,道長(zhǎng)泛鸟,這世上最難降的妖魔是什么蝠咆? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮北滥,結(jié)果婚禮上刚操,老公的妹妹穿的比我還像新娘。我一直安慰自己再芋,他們只是感情好菊霜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著济赎,像睡著了一般鉴逞。 火紅的嫁衣襯著肌膚如雪记某。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天构捡,我揣著相機(jī)與錄音辙纬,去河邊找鬼。 笑死叭喜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蓖谢。 我是一名探鬼主播捂蕴,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼闪幽!你這毒婦竟也來了啥辨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盯腌,失蹤者是張志新(化名)和其女友劉穎溉知,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腕够,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡级乍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帚湘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玫荣。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖大诸,靈堂內(nèi)的尸體忽然破棺而出捅厂,到底是詐尸還是另有隱情,我是刑警寧澤资柔,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布焙贷,位于F島的核電站,受9級(jí)特大地震影響贿堰,放射性物質(zhì)發(fā)生泄漏辙芍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一羹与、第九天 我趴在偏房一處隱蔽的房頂上張望沸手。 院中可真熱鬧,春花似錦注簿、人聲如沸契吉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捐晶。三九已至菲语,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惑灵,已是汗流浹背山上。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留英支,地道東北人佩憾。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像干花,于是被迫代替她去往敵國(guó)和親妄帘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • iOS開發(fā)系列--網(wǎng)絡(luò)開發(fā) 概覽 大部分應(yīng)用程序都或多或少會(huì)牽扯到網(wǎng)絡(luò)開發(fā)池凄,例如說新浪微博抡驼、微信等,這些應(yīng)用本身可...
    lichengjin閱讀 3,661評(píng)論 2 7
  • Scrum Scrum is a framework for developing and sustaining ...
    寒十四閱讀 264評(píng)論 0 0
  • 有一位老婦肿仑,她特別喜愛吃洋蔥頭致盟。她長(zhǎng)著一只像洋蔥頭的鼻子,當(dāng)她嘴饞的時(shí)候尤慰,又恰好家里的洋蔥頭斷頓馏锡,她會(huì)摸著鼻子說:...
    楚蓮若閱讀 449評(píng)論 2 6
  • 本文參考:https://morvanzhou.github.io/tutorials/machine-learn...
    小董不太懂閱讀 956評(píng)論 0 2
  • 王青在回家的路上撿了一只貓,臟兮兮的伟端,渾身都是泥眷篇。對(duì)于他為什么要撿它回家,他解釋為愛心泛濫荔泳,不忍心看著它淋著雨蕉饼。 ...
    我是小阿旭吖閱讀 388評(píng)論 0 0