iOS 給UILabel添加點擊事件

級別:★☆☆☆☆
標籤:「UILabel」「TTTAttributedLabel 基本使用」「TTTAttributedLabel 實現」
做者: WYW
審校: QiShare團隊php


前言:筆者最近須要實現給 UILabel 中的連接添加點擊事件的功能。使用 so.com 查了下,發現 TTTAttributedLabel 的封裝程度比較好。整理了 TTTAttributedLabel 的基本使用,及部分實現。git

TTTAttributedLabel 的基本使用

TTTAttributedLabel.hTTTAttributedLabel.m 放到項目中

遵照 TTTAttributedLabelDelegate 協議

// 遵照TTTAttributedLabelDelegate協議
@interface ViewController () <TTTAttributedLabelDelegate>
複製代碼

建立 TTTAttributedLabel 實例及相應配置

建立 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);
}
複製代碼

實現TTTAttributedLabelDelegate 代理方法

在以下代理方法中查看當前點擊的連接。數組

//! 實現代理方法
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
    
    NSLog(@"url信息:%@", url);
}
複製代碼

TTTAttributedLabel 部分實現

設置 attriLabel.text = @"Fork me on GitHub! (https://github.com/mattt/TTTAttributedLabel/)"; 查看了 TTTAttributedLabel 的大概實現流程。bash

設置 TTTAttributedLabel 用戶交互可用。

self.userInteractionEnabled = YES;
複製代碼

TTTAttributedLabel 連接指定了默認連接樣式

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]; 就和上述的「自動檢測」連接的內容是同樣的。

點擊Label 的連接文字後,查找到對應 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);
複製代碼
  • 根據當前label相對自身座標系 frame 及 屬性字符串 建立CT座標系所需的frame
CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, textRect);
    CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL);
複製代碼
  • 肯定UILabel 當前在CT 座標系顯示佔用的行數 CFArrayGetCount(lines)
NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
複製代碼
  • 遍歷CT 座標中文字的每一行,找到當前點擊點所在的行,計算出點擊點相對於當前行的座標,並計算出當前索引。
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的啓動流程
奇舞週刊

相關文章
相關標籤/搜索