iOS 富文本 url實現(xiàn)點擊

說說和聊天界面,會要求把鏈接牛曹,@佛点,##等,特殊的字符串能點擊,可能它涉及到CoreText層超营,自己寫起來比較麻煩鸳玩,平常忙著做項目,也沒時間去研究它演闭,都用的第三方不跟,最近項目不緊,自己研究了一下米碰。寫篇文章窝革,加深一下印象

這個工程主要創(chuàng)建4個類,MainViewController類屬于c層吕座,MainCell和RichLabel屬于view層虐译,RichLink屬于model層,這四個類中MainViewController吴趴,MainCell漆诽,RichLink這三個不用看,我也沒注釋史侣,就是一些簡單的創(chuàng)建UILabel控件的代碼拴泌,主要看RichLabel

下面是MainViewController.m里面的代碼,.h沒代碼就不寫出來了

#import "MainViewController.h"

#import "MainCell.h"

@interface MainViewController ()

/** 注釋 */

@property (nonatomic,strong) NSArray *dataArr;

@end

@implementation MainViewController

- (void)viewDidLoad {

[super viewDidLoad];

_dataArr = @[@"【國防部:中國首艘國產(chǎn)航母舾裝工作進展順利】中國國防部31號表示惊橱,中國首艘航母進展順利蚪腐,目前正進行系統(tǒng)設(shè)備調(diào)試和舾裝施工,并將全面開展系泊試驗税朴。系泊試驗后還將進行海上試驗回季,以及交付部隊階段,相信整個后續(xù)工作正林,大概需時至少兩年泡一。 Via鳳凰早班車",

@"特朗普將捐100萬美元幫助颶風災(zāi)民渡過難關(guān) 2日再訪災(zāi)區(qū)】美國白宮發(fā)言人桑德斯8月31日透露,總統(tǒng)特朗普將以個人名義捐出100萬美元觅廓,幫助受颶風“哈維”影響的得克薩斯州和路易斯安那州災(zāi)民救災(zāi)鼻忠。此外,特朗普夫婦還將于9月2日再次前往受災(zāi)地區(qū)視察杈绸。http://t.cn/RNfnGvO",

@"建筑工人在商場彈起優(yōu)美鋼琴曲 網(wǎng)友:隱士高人 鐵漢柔情】“可能為了生活帖蔓,需要放下夢想”,30日瞳脓,香港曹先生在商場里拍下這樣一幕:一位身著工地制服的建筑工在認真彈奏鋼琴塑娇。許多網(wǎng)友被這美妙的音樂和投入的彈奏所感動~@新京報 http://t.cn/RN5w8vc",

@"【出租車駕駛員從業(yè)資格考試將公開題庫 兩考合一】交通運輸部將對出租汽車駕駛員資格考試進行改革。交通運輸部運輸服務(wù)司副司長王繡春表示劫侧,要對出租車駕駛員從業(yè)資格考試進一步優(yōu)化完善埋酬,將實行全國公共科目考試和區(qū)域科目考試“兩考合一”哨啃,優(yōu)化題庫試題、公開考試題庫写妥,強化便民利民服務(wù)拳球。 ...全文: http://m.weibo.cn/1730243272/4147093503116821",

@"【“十一”黃金周國內(nèi)游將達6.5億人次 又能用“朋友圈”環(huán)游世界了呢[doge]】《2017十一黃金周旅游趨勢預(yù)測報告》稱,今年“十一”將有6.5億人次國內(nèi)游耳标、超600萬人出境游醇坝。專家建議,盡量錯峰出游次坡。看“朋友圈”環(huán)游世界的時候又到了...http://t.cn/RNGelZ2 “十一”你選擇在家還是出去玩?",

@"#助威國足戰(zhàn)在一起# 【郜林點球破門 中國1-0烏茲別克斯坦】中國隊1:0戰(zhàn)勝烏茲別克斯坦画畅,武漢體育中心五萬人賽后大合唱《怒放的生命》砸琅,教練@米盧、球員@郜林 等紛紛微博發(fā)聲轴踱,祝賀國足症脂,中國贏下去! http://t.cn/RNqBrpG",

@"#薦書#今日推薦:《理解人性》淫僻。對人類來說诱篷,最難的是認識自己和改變自己。在書中雳灵,作者運用個性心理學的原理棕所,對人的性格進行了科學的剖析,著重強調(diào)人的社會性和社會感悯辙,強調(diào)個人的人生觀和價值觀在形成性格的過程中所起的作用琳省。 http://t.cn/RNqQjdK"];

}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return _dataArr.count;

}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

return [MainCell getCellHeight:_dataArr[indexPath.row]];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *identifier = @"MainCell";

MainCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

if (cell == nil) {

cell = [[MainCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];

cell.selectionStyle = UITableViewCellSelectionStyleNone;

}

cell.content = _dataArr[indexPath.row];

return cell;

}

@end

下面是cell.h文件里面的代碼

#import<UIKit/UIKit.h>

@class RichLabel;

@interface MainCell : UITableViewCell

@property (strong, nonatomic) RichLabel *contentLb;

@property (nonatomic, copy)NSString *content;

+ (CGFloat)getCellHeight:(NSString *)content;

@end

下面是cell.m文件里面的代碼

#import "MainCell.h"

#import "RichLabel.h"

#define kEdge 15

@implementation MainCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{

self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

if (self) {

self.contentLb = [[RichLabel alloc] initWithFrame:CGRectZero];

self.contentLb.userInteractionEnabled = YES;

self.contentLb.numberOfLines = 0;

self.contentLb.lineBreakMode = NSLineBreakByWordWrapping;

self.contentLb.font = kFontSize;

[self.contentView addSubview:self.contentLb];

}

return self;

}

+(CGSize)sizeOfmyText:(NSString *)text font:(UIFont*)myfont width:(CGFloat)mywidth lineSpacing:(CGFloat)lineSpacing

{

if([text isEqual:[NSNull null]] || text==nil || [text isEqualToString:@""])

return CGSizeMake(0.0f, 0.0f);

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];

paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;

if (lineSpacing > 0) {

paragraphStyle.lineSpacing = lineSpacing;

}

NSDictionary *attributes = @{NSFontAttributeName:myfont, NSParagraphStyleAttributeName:paragraphStyle.copy};

CGSize labelsize = [text boundingRectWithSize:CGSizeMake(mywidth, 2000) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;

return labelsize;

}

- (void)setContent:(NSString *)content{

_content = content;

self.contentLb.content = content;

CGFloat w = self.contentView.frame.size.width - 2*kEdge;

self.contentLb.frame = CGRectMake(kEdge, 0, w, [MainCell sizeOfmyText:content font:kFontSize width:w lineSpacing:0].height);

}

+ (CGFloat)getCellHeight:(NSString *)content{

return [MainCell sizeOfmyText:content font:kFontSize width:[UIScreen mainScreen].bounds.size.width - 2*kEdge lineSpacing:0].height;

}

@end

下面是RichLink.h的代碼,.m沒代碼,就不寫出來了

#import<Foundation/Foundation.h>

@interface RichLink : NSObject

@property (nonatomic, copy) NSString *linkStr;

@property (nonatomic, assign) NSRange range;

@end

RichLabel.h代碼,沒什么好解釋的

#import <UIKit/UIKit.h>

#define kFontSize [UIFont systemFontOfSize:14]

@interface RichLabel : UILabel

@property (nonatomic, copy)NSString *content;

@end

上面的不用看躲撰,上面寫那么多针贬,就為了方便你粘貼復(fù)制到你工程,,這才本文重點拢蛋,RichLabel.m代碼

#import "RichLabel.h"

#import <CoreText/CoreText.h>

#import "RichLink.h"

@implementation RichLabel

- (void)setContent:(NSString *)content{? ??

_content = content;? ??

self.attributedText = [self matchesAttributedString:content];

}

#pragma mark 處理鏈接顏色

- (NSAttributedString *)matchesAttributedString:(NSString *)str{? ??

NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:str];? ??

for (NSTextCheckingResult *match in self.matchesArr)? ? {? ? ? ??

[attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:match.range];? ??

}? ? ? ??

[attrStr addAttribute:NSFontAttributeName value:kFontSize range:NSMakeRange(0, attrStr.length)];? ??

return attrStr;

}

//通過正則表達式匹配出來的特殊文本放到一個數(shù)組

- (NSArray *)matchesArr{? ??

NSError *error;? ??

NSString *urlPattern = @"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";//匹配url? ?

?NSString *emotionPattern = @"\\[[0-9a-zA-Z\\u4e00-\\u9fa5]+\\]";//匹配自定義表情 帶[]? ??

NSString *atPattern = @"@[0-9a-zA-Z\\u4e00-\\u9fa5]+";//匹配@? ? NSString *topicPattern = @"#[0-9a-zA-Z\\u4e00-\\u9fa5]+#";//匹配##? ?

?NSString *pattern = [NSString

stringWithFormat:@"%@|%@|%@|%@",urlPattern,emotionPattern,atPattern,topicPattern];? ? ? ? NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? options:NSRegularExpressionCaseInsensitive? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:&error];? ? NSArray *arrayOfAllMatches = [regex matchesInString:_content options:0 range:NSMakeRange(0, [_content length])];? ??

return arrayOfAllMatches;

}

#pragma mark 點擊事件

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event

{

UITouch *touch = touches.allObjects[0];

CGPoint touchPos = [touch locationInView:self];

//? ? __weak typeof (HWStatusTextLabel *)weakSelf = self;

[self getTapFrameWithTouchPoint:touchPos result:^(NSString *string, NSRange range, NSInteger index) {

NSLog(@"string : %@ NSStringFromRange : %@ index : %ld",string,NSStringFromRange(range),(long)index);

}];

}

#pragma mark - getTapFrame

- (BOOL)getTapFrameWithTouchPoint:(CGPoint)point result:(void(^) (NSString *string,NSRange range , NSInteger index))resultBlock{

//判斷點擊的區(qū)域是否超出label的范圍

CGFloat verticalOffset = 0.0f;

if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -verticalOffset),point)) return NO;

//這兩句 創(chuàng)建了一個路徑桦他,用于表示文本在此區(qū)域內(nèi)繪制著。Mac下Core Text支持在各種形狀中繪制文本谆棱,比如矩形和橢圓快压。但iOS下僅支持矩形。在本例中础锐,使用整個視圖作為文本繪制的路徑嗓节,直接將self.bounds轉(zhuǎn)變?yōu)橐粋€CGPath

CGMutablePathRef path = CGPathCreateMutable();

CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), self.bounds.size.height));

//CTFramesetter是繪制Core Text文本的最重要的一個類。用它來管理所有的字體引用和文本繪制塊〗跃現(xiàn)在你需要知道的僅僅是使用 CTFramesetterCreateWithAttributedString函數(shù)創(chuàng)建一個 CTFramesetter拦宣。在創(chuàng)建出CTFramesetter之后,我們可以用 CTFramesetterCreateFrame 得到文本一個渲染范圍,以及繪制文本時文本顯示范圍鸵隧,方法最終返回一個文本繪制塊

//我們不能直接拿CGRect 來得到文本的范圍绸罗,因為用CGRect得到的范圍,和真正文本顯示的范圍豆瘫,還是有偏差的

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedText);

CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);

//得到繪制的這塊文本中有多少行字符串珊蟀,放到類型為CFArrayRef里,用處和我們的數(shù)組很像

CFArrayRef lines = CTFrameGetLines(frame);

if (!lines) {//如果沒有直接返回

return NO;

}

//得到行數(shù)

CFIndex count = CFArrayGetCount(lines);

//定一個origins數(shù)組外驱,里面存放CGPoint的元素育灸,開辟count個空間

CGPoint origins[count];

//得到每一行的坐標原點存放到origins里面

CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);

//因為在UIKit中坐標系原點在左上方,CoreText中在左下方昵宇,左下方的

CGAffineTransform transform = [self transformForCoreText];

//循環(huán)每一行

for (CFIndex i = 0; i < count; i++) {

//得到每一行的原點

CGPoint linePoint = origins[i];

//得到每一行

CTLineRef line = CFArrayGetValueAtIndex(lines, i);

//這個會涉及到字形的問題磅崭,這有個鏈接,可以看一下瓦哎,blog.csdn.net/fengsh998/article/details/8701738

//得到每行的frame

CGRect flippedRect = [self getLineBounds:line point:linePoint];

//坐標轉(zhuǎn)換后得到的frame

CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);

rect = CGRectInset(rect, 0, 0);

//在一行文本的范圍內(nèi)砸喻,y軸向內(nèi)偏移verticalOffset距離

rect = CGRectOffset(rect, 0, -verticalOffset);

//判斷點擊的點是否在文本范圍內(nèi),如果在蒋譬,證明點擊的是這一行

if (CGRectContainsPoint(rect, point)) {

//因為每行算frame高度的時候割岛,沒有加上行距,所以每行的高度犯助,要比總體的高度除以行數(shù)癣漆,算出來的要少。所以這里要重新算點擊的ponit也切,求出相對于點擊當前行原點的point扑媚,防止點到每兩行之間的空白處,也相應(yīng)點擊事件

CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), point.y - CGRectGetMinY(rect));

//獲取在當前行的下標

CFIndex index = CTLineGetStringIndexForPosition(line, relativePoint);

//鏈接個數(shù)

NSInteger link_cout = self.linkArr.count;

for (int j = 0; j < link_cout; j++) {

RichLink *link = self.linkArr[j];

//每個鏈接的范圍

NSRange link_range = link.range;

//判斷下標是否在連接的范圍雷恃,如果是疆股,證明點擊的該鏈接

if (NSLocationInRange(index, link_range)) {

if (resultBlock) {

resultBlock (link.linkStr,link.range,(NSInteger)j);

}

return YES;

}

}

}

}

return NO;

}

- (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point

{

CGFloat ascent = 0.0f;

CGFloat descent = 0.0f;

CGFloat leading = 0.0f;

CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

CGFloat height = ascent + descent;//這個地方我們沒有把行距加上,防止點到空白處倒槐,也能觸發(fā)點擊事件

//這個point.y - ascent需要說一下旬痹,看我上面發(fā)的鏈接的博客,我們知道讨越,她的pont.y在基線的左側(cè)两残,上行高度ascent從原點到字體中最高的字形的頂部的距離

return CGRectMake(point.x, point.y - ascent, width, height);

}

//先翻轉(zhuǎn),再向下平移self.bounds.size.height把跨,本來原點在左下方人弓,現(xiàn)在在左上方

- (CGAffineTransform)transformForCoreText

{

return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.0f, -1.0f);

}

//對匹配出來的特殊文本進行model封裝,放到linkArr數(shù)組着逐,RichLink存放的有該文本字符串和范圍

- (NSArray *)linkArr

{

NSMutableArray *linkArr = [NSMutableArray arrayWithCapacity:1];

for (NSTextCheckingResult *match in self.matchesArr)

{

NSString *substringForMatch = [self.text substringWithRange:match.range];

NSRange range = [self.text rangeOfString:substringForMatch];

RichLink *link = [RichLink new];

link.linkStr = substringForMatch;

link.range = range;

[linkArr addObject:link];

}

return linkArr;

}

@end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崔赌,一起剝皮案震驚了整個濱河市意蛀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌健芭,老刑警劉巖县钥,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慈迈,居然都是意外死亡若贮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門痒留,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谴麦,“玉大人,你說我怎么就攤上這事伸头∠敢疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵熊锭,是天一觀的道長。 經(jīng)常有香客問我雪侥,道長碗殷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任速缨,我火速辦了婚禮锌妻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旬牲。我一直安慰自己仿粹,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布原茅。 她就那樣靜靜地躺著吭历,像睡著了一般。 火紅的嫁衣襯著肌膚如雪擂橘。 梳的紋絲不亂的頭發(fā)上晌区,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天,我揣著相機與錄音通贞,去河邊找鬼朗若。 笑死,一個胖子當著我的面吹牛昌罩,可吹牛的內(nèi)容都是我干的哭懈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茎用,長吁一口氣:“原來是場噩夢啊……” “哼遣总!你這毒婦竟也來了睬罗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤彤避,失蹤者是張志新(化名)和其女友劉穎傅物,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琉预,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡董饰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了圆米。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卒暂。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娄帖,靈堂內(nèi)的尸體忽然破棺而出也祠,到底是詐尸還是另有隱情,我是刑警寧澤近速,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布诈嘿,位于F島的核電站,受9級特大地震影響削葱,放射性物質(zhì)發(fā)生泄漏奖亚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一析砸、第九天 我趴在偏房一處隱蔽的房頂上張望昔字。 院中可真熱鬧,春花似錦首繁、人聲如沸作郭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夹攒。三九已至,卻和暖如春挂捅,著一層夾襖步出監(jiān)牢的瞬間芹助,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工闲先, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留状土,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓伺糠,卻偏偏與公主長得像蒙谓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子训桶,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

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