你們都用過微博,微博有用戶「@盼盼」,話題「#怎麼追漂亮女孩?#」,還有網絡連接「http: //...」,還有各類協議「《FBI WARNING...》」,實際項目中要求給這些特殊的字符着色,就像 demo 這樣。這篇文章將帶你封裝這個功能,而且在實際開發中 0 行代碼集成。git
若是你趕項目沒時間看源碼,只須要看一下下面的使用就能夠了。github
JPLabel
拖進你的項目。Xib
或者 SB
中,直接把你的 UILabel
的類別改爲 JPLabel
,你的 Label
就自動擁有這些功能了。UILabel
繼承自 JPLabel
就自動擁有這些功能了。#import "ViewController.h"
#import "JPLabel.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet JPLabel *textLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textLabel.text = @"#JPLabel# 用於匹配字符串的內容顯示, 用戶:@盼盼, 包括話題:#怎麼追漂亮女孩?#, 連接:https://github.com/Chris-Pan/JPLabel, 協議:《退款政策》";
// 非匹配內容文字顏色
self.textLabel.jp_commonTextColor = [UIColor colorWithRed:112.0/255 green:93.0/255 blue:77.0/255 alpha:1];
// 點選高亮文字顏色
self.textLabel.jp_textHightLightBackgroundColor = [UIColor colorWithRed:237.0/255 green:213.0/255 blue:177.0/255 alpha:1];
// 匹配文字顏色
[self.textLabel setHightLightTextColor:[UIColor colorWithRed:132.0/255 green:77.0/255 blue:255.0/255 alpha:1] forHandleStyle:HandleStyleUser];
[self.textLabel setHightLightTextColor:[UIColor colorWithRed:9.0/255 green:163.0/255 blue:213.0/255 alpha:1] forHandleStyle:HandleStyleLink];
[self.textLabel setHightLightTextColor:[UIColor colorWithRed:254.0/255 green:156.0/255 blue:59.0/255 alpha:1] forHandleStyle:HandleStyleTopic];
[self.textLabel setHightLightTextColor:[UIColor colorWithRed:255.0/255 green:69.0/255 blue:0.0/255 alpha:1] forHandleStyle:HandleStyleAgreement];
// 自定義匹配的文字和顏色
self.textLabel.jp_matchArr = @[
@{
@"string" : @"高亮顯示",
@"color" : [UIColor colorWithRed:0.55 green:0.86 blue:0.34 alpha:1]
}
];
// 匹配到合適內容的回調
self.textLabel.jp_tapOperation = ^(UILabel *label, HandleStyle style, NSString *selectedString, NSRange range){
// 你要作的事
NSLog(@"%@", selectedString);
};
}
#pragma mark JPLabelDelegate
-(void)jp_label:(JPLabel *)label didSelectedString:(NSString *)selectedStr forStyle:(HandleStyle)style inRange:(NSRange)range{
// 你想要作的事
NSLog(@"代理打印 %@", selectedStr);
}
@end
複製代碼
Objective-C
的數組只能存儲繼承自 NSObject
的對象。這個功能有一個需求,咱們使用正則表達式匹配到的結果是一個 NSRange
的實例對象,咱們須要將匹配結果,也就是多個 NSRange
實例緩存起來,由於後期要作點擊結果的匹配。使用簡單的 "@()"
包裝是不能將一個結構體轉換成爲對象的。NSValue
這個類,採起先將 NSRange
實例轉換爲 NSValue
,咱們把 NSValue
存在數組當中。NSValue
轉換爲 NSRange
,這樣就能夠間接的存儲咱們要的值。 代碼以下:// 存值
NSMutableArray *ranges = [NSMutableArray array];
for (NSTextCheckingResult *result in results) {
// 將結構體保存到數組
// 先用一個變量接受結構體
NSRange range = result.range;
NSValue *value = [NSValue valueWithBytes:&range objCType:@encode(NSRange)];
[ranges addObject:value];
}
// 取值
for (NSValue *value in ranges) {
NSRange range;
[value getValue:&range];
...
}
複製代碼
let str ="132468842823"
// 1.建立規則
let pattern = "^1[3578][0-9]{9}$"
// 2.建立正則表達式對象
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
return
}
// 3.開始匹配
let results = regex.matchesInString(str, options: [], range: NSRange(location: 0, length: str.characters.count))
// 4.遍歷匹配結果
for result in results {
print((str as NSString).substringWithRange(result.range))
}
複製代碼
先來看一張我從 Text Kit
文檔中找出來的圖:正則表達式
這裏我簡單的描述一下每一個類的做用,具體你若是感興趣,能夠本身去看文檔,或者看這篇文檔的中文翻譯文章。swift
NSTextStorage
是 NSMutableAttributedString
子類,存儲用於顯示的文本,同時也管理一組 NSLayoutManager
對象。NSLayoutManager
負責將 NSTextStorage
中的文本顯示到 NSTextContainer
區域。它將 Unicode
字符轉換成 glyph
,並監督 glyph
在 NSTextContainer
對象所定義的區域中佈局的過程。NSTextContainer
負責保存文本顯示的區域。主題思路分爲兩步走:數組
TextKit
中的三個類,還有一些默認的顏色配置,也就是前6行代碼。-(void)setup{
_textStorage = [NSTextStorage new];
_layoutManager = [NSLayoutManager new];
_textContainer = [NSTextContainer new];
_jp_commonTextColor = [UIColor colorWithRed:162.0/255 green:162.0/255 blue:162.0/255 alpha:162.0/255];
_jp_textHightLightBackgroundColor = [UIColor colorWithWhite:0.7 alpha:0.2];
_linkHightColor = _topicHightColor = _agreementHightColor = _userHightColor = [UIColor colorWithRed:64.0/255 green:64.0/255 blue:64.0/255 alpha:1];
[self prepareTextSystem];
}
複製代碼
// 準備文本系統
-(void)prepareTextSystem{
// 0.準備文本
[self prepareText];
// 1.將佈局添加到storeage中
[self.textStorage addLayoutManager:self.layoutManager];
// 2.將容器添加到佈局中
[self.layoutManager addTextContainer:self.textContainer];
// 3.讓label能夠和用戶交互
self.userInteractionEnabled = YES;
// 4.設置間距爲0
self.textContainer.lineFragmentPadding = 0;
}
複製代碼
// 佈局子控件
-(void)layoutSubviews{
[super layoutSubviews];
// 設置容器的大小爲Label的尺寸
self.textContainer.size = self.frame.size;
}
複製代碼
// 若是用戶沒有設置lineBreak,則全部內容會繪製到同一行中,所以須要主動設置
-(NSMutableAttributedString *)addLineBreak:(NSAttributedString *)attrString{
NSMutableAttributedString *attrStringM = [attrString mutableCopy];
if (attrStringM.length == 0) return attrStringM;
NSRange range = NSMakeRange(0, 0);
NSMutableDictionary *attributes = [[attrStringM attributesAtIndex:0 effectiveRange:&range] mutableCopy];
NSMutableParagraphStyle *paragraphStyle = [attributes[NSParagraphStyleAttributeName] mutableCopy];
if (paragraphStyle != nil) {
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
}
else{
paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
[attrStringM setAttributes:attributes range:range];
}
return attrStringM;
}
複製代碼
// 準備文本
-(void)prepareText{
// 1.準備字符串
NSAttributedString *attrString = nil;
if (self.attributedText != nil) {
attrString = self.attributedText;
}
else if (self.text != nil){
attrString = [[NSAttributedString alloc]initWithString:self.text];
}
else{
attrString = [[NSAttributedString alloc]initWithString:@""];
}
if (attrString.length == 0) return;
self.selectedRange = NSMakeRange(0, 0);
// 2.設置換行模型
NSMutableAttributedString *attrStringM = [self addLineBreak:attrString];
// 3.給文本添加顯示字號和顏色
NSDictionary *attr;
attr = @{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.jp_commonTextColor
};
[attrStringM setAttributes:attr range:NSMakeRange(0, attrStringM.length)];
// 4.設置textStorage的內容
[self.textStorage setAttributedString:attrStringM];
// 5.匹配URL
NSArray *linkRanges = [self getLinkRanges];
self.linkRangesArr = linkRanges;
for (NSValue *value in linkRanges) {
NSRange range;
[value getValue:&range];
[self.textStorage addAttribute:NSForegroundColorAttributeName value:self.linkHightColor range:range];
}
...
// 9.更新顯示,從新繪製
[self setNeedsDisplay];
}
複製代碼
setNeedsDisplay
的時候,系統會觸發 drawRect
方法,咱們在這裏繪製文字並顯示是最理想的。這裏注意,咱們這裏自定義了繪製文字了,不須要系統再繪製默認的文字,要實現這個只須要不調用父類的實現,只要不調用 [super drawRect:rect]
就能夠達成效果了。// 重寫drawTextInRect方法
-(void)drawRect:(CGRect)rect{
// 不調用super就不會繪製原有文字
// [super drawRect:rect];
// 2.繪製字形
// 須要繪製的範圍
NSRange range = NSMakeRange(0, self.textStorage.length);
[self.layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointZero];
}
複製代碼
到這裏爲止,就完成第一步了,也就是自定義顯示格式,給關鍵字着色。緩存
要實現點擊監聽,就先要把 Label
的 userInteractionEnabled
打開,由於 UILabel
默認是關閉了的。網絡
touchesBegan
方法,在這個方法裏拿到用戶點擊的點,而後進入到 [self getSelectRange:selectedPoint]
方法裏。-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 0.記錄點擊
self.isSelected = YES;
// 1.獲取用戶點擊的點
CGPoint selectedPoint = [[touches anyObject]locationInView:self];
// 2.獲取該點所在的字符串的range
self.selectedRange = [self getSelectRange:selectedPoint];
// 3.是否處理了事件
if (self.selectedRange.length == 0) {
[super touchesBegan:touches withEvent:event];
}
}
複製代碼
self.layoutManager
保存了全部的文字顯示排版的數據,而且每一個字符在 self.layoutManager
中都有一個索引編號,這個編號從 0 開始累加。根據傳進來的點,利用 self.layoutManager
裏保存的文本數據,咱們就能夠找到這個點對應那個字符的序號。拿到這個序號,咱們就能夠在咱們本身用正則表達式匹配的結果中,查詢這個序號是否有對應的關鍵字。若是有,就把這個關鍵字的 NSRange
實例返回,若是不存在就返回一個空值。-(NSRange)getSelectRange:(CGPoint)selectPoint{
// 0.若是屬性字符串爲nil,則不須要判斷
if (self.textStorage.length == 0) return NSMakeRange(0, 0);
// 1.獲取選中點所在的下標值(index)
NSUInteger index = [self.layoutManager glyphIndexForPoint:selectPoint inTextContainer:self.textContainer];
// 2.判斷下標在什麼內
// 2.1.判斷是不是一個連接
for (NSValue *value in self.linkRangesArr) {
NSRange range;
[value getValue:&range];
if (index > range.location && index < range.location + range.length) {
[self setNeedsDisplay];
self.tapStyle = HandleStyleLink;
return range;
}
}
...
return NSMakeRange(0, 0);
}
複製代碼
touchesEnded
方法,當匹配到用戶點擊的位置有關鍵字的時候,應該作出適當的反應,好比這裏採用的讓關鍵字區域背景顏色高亮一個顏色。要實現這個功能,咱們只須要調用 [self setNeedsDisplay]
方法,系統自動會去從新繪製。同時,取出用戶點擊的關鍵字段,經過回調把數據傳遞出去。-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (self.selectedRange.length == 0) {
[super touchesEnded:touches withEvent:event];
return;
}
// 0.記錄鬆開
self.isSelected = NO;
// 2.從新繪製
[self setNeedsDisplay];
// 3.取出內容
NSString *selectedString = [[self.textStorage string] substringWithRange:self.selectedRange];
// 3.回調
switch (self.tapStyle) {
case HandleStyleAgreement:{
__weak typeof(self) weakSelf = self;
if (self.jp_tapOperation) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
self.jp_tapOperation(strongSelf, HandleStyleAgreement, selectedString, strongSelf.selectedRange);
}
}
break;
...
default:
break;
}
}
複製代碼
drawRect
方法裏添加一個開關,當檢測到用戶點擊的時候,就打開開關,繪製點擊關鍵字背景。若是沒有點擊,或者點擊區域不包含關鍵字,則關閉繪製背景開關。// 重寫drawTextInRect方法
-(void)drawRect:(CGRect)rect{
// 不調用super就不會繪製原有文字
// [super drawRect:rect];
// 1.繪製背景
if (self.selectedRange.length != 0) {
// 2.0.肯定顏色
UIColor *selectedColor = self.isSelected ? self.jp_textHightLightBackgroundColor : [UIColor clearColor];
// 2.1.設置顏色
[self.textStorage addAttribute:NSBackgroundColorAttributeName value:selectedColor range:self.selectedRange];
// 2.2.繪製背景
[self.layoutManager drawBackgroundForGlyphRange:self.selectedRange atPoint:CGPointMake(0, 0)];
}
}
複製代碼
到這裏爲止,就能夠0行代碼集成關鍵字高亮點選響應封裝了。源代碼在這裏GIT地址。app
2016.9.9 爲框架加入自定義字符串高亮顯示功能,具體使用以下:框架
// 自定義匹配的文字和顏色
self.textLabel.jp_matchArr = @[
@{
@"string" : @"高亮顯示",
@"color" : [UIColor colorWithRed:0.55 green:0.86 blue:0.34 alpha:1]
}
];
複製代碼
效果以下:工具