說說和聊天界面,會要求把鏈接牛曹,@佛点,##等,特殊的字符串能點擊,可能它涉及到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