這周在做消息頁面的文本消息展示時(shí),遇到了選擇什么樣的控件去展示文本消息的問題。
在一個(gè)消息列表中,相比于圖片,語音等類型的消息,文本消息是最常見的,但是文本消息的展示也是最復(fù)雜的。在文本消息中包含多種信息,比如鏈接,電話號碼等等特殊的字符。不僅要在UI展示上對這些特殊字符進(jìn)行處理,也就是富文本的展示,還要對其設(shè)置相應(yīng)的點(diǎn)擊事件。
對于常見的系統(tǒng)控件UILabel和UITextView,都可以實(shí)現(xiàn)富文本的展示。
區(qū)別在于:
1、UITextView有系統(tǒng)相應(yīng)的代理方法可以對鏈接字符和電話字符處理相應(yīng)的點(diǎn)擊事件。而UILabel沒有這樣的功能。
2、UITextView和UILabel都可以實(shí)現(xiàn)自動(dòng)換行。由于UILabel不可編輯,也不可滑動(dòng)。而UITextView繼承自UIScrollView,是可編輯的,也是可滑動(dòng)。但是UITextView在展示時(shí),距離上下左右都分別有8px的距離,因此使用計(jì)算字符串長度和高度的方法 [NSString sizeWithFont:constrainedToSize:lineBreakMode:]時(shí),需要注意這些邊距細(xì)節(jié)的加減。因此,UILabel在展示消息時(shí)可以更方便的計(jì)算自適應(yīng)高度,而UITextView會(huì)比較麻煩一些。
這個(gè)自定義的控件,是這周在看網(wǎng)易云信的源碼時(shí)學(xué)習(xí)到的。
M80AttributedLabel是一個(gè)繼承自UIView的控件,實(shí)現(xiàn)了label的基本功能外,還封裝了一些富文本展示的方法,可以自動(dòng)的檢測鏈接,設(shè)置鏈接的排版樣式,以及相應(yīng)的點(diǎn)擊事件的代理方法。
在這里著重看了M80AttributedLabel如何實(shí)現(xiàn)點(diǎn)擊事件的過程。
1、根據(jù)UIView的觸摸代理方法獲取到當(dāng)前觸摸點(diǎn)的位置坐標(biāo);
2、根據(jù)當(dāng)前文本的frame,得到frame所對應(yīng)的行數(shù)組( CFArrayRef)及每行的原點(diǎn)坐標(biāo)數(shù)組;
3、遍歷行數(shù)組,通過一系列矩陣變換和位置操作,取得當(dāng)前點(diǎn)擊區(qū)域的文字在整段文字中的索引;
4、在正則匹配到的URL數(shù)組中(該數(shù)組的每個(gè)元素存儲(chǔ)了URL及其對應(yīng)的位置),查找該索引所對應(yīng)的URL;
5、找到對應(yīng)的URL后,就會(huì)通過safari打開該鏈接。
下面是使用M80AttributedLabel的一個(gè)小demo:
- (void)setM80AttributedLabelText
{
? ? NSString *string1 = @"第一條文本消息https://www.baidu.com";
? ? [self.m80Label setText:string1];
}
#pragma mark - M80AttributedLabelDelegate
- (void)m80AttributedLabel:(M80AttributedLabel *)label
? ? ? ? ? ? clickedOnLink:(id)linkData{
? ? NSString *link = (NSString *)linkData;
? ? NSLog(@"%@", link);
? ? NSURLComponents *components = [[NSURLComponents alloc] initWithString:link];
? ? if (components)
? ? {
? ? ? ? if (!components.scheme)
? ? ? ? {
? ? ? ? ? ? //默認(rèn)添加 http
? ? ? ? ? ? components.scheme = @"http";
? ? ? ? }
? ? ? ? [[UIApplication sharedApplication] openURL:[components URL]];
? ? }
}
#pragma mark - getter/setter
- (M80AttributedLabel *)m80Label
{
? ? if (!_m80Label) {
? ? ? ? _m80Label = [[M80AttributedLabel alloc] initWithFrame:CGRectMake(100, 60, 100, 100)];
? ? ? ? _m80Label.numberOfLines = 0;
? ? ? ? _m80Label.lineBreakMode = NSLineBreakByWordWrapping;
? ? ? ? _m80Label.backgroundColor = [UIColor clearColor];
? ? ? ? _m80Label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
? ? ? ? _m80Label.textAlignment = kCTTextAlignmentLeft;
? ? ? ? _m80Label.font = [UIFont systemFontOfSize:16];
? ? ? ? _m80Label.textColor = [UIColor blackColor];
? ? ? ? _m80Label.highlightColor = [UIColor redColor];
? ? ? ? _m80Label.linkColor = [UIColor yellowColor];
? ? ? ? _m80Label.underLineForLink = NO;
? ? ? ? _m80Label.userInteractionEnabled = YES;
? ? ? ? _m80Label.delegate = self;
? ? }
? ? return _m80Label;
}
YYLabel也是一個(gè)繼承自UIView的自定義控件,是YYKit下的一部分。據(jù)說YYKit很強(qiáng)大,但是目前用到的還只是其中的YYLabel,更強(qiáng)大的功能,下來再慢慢挖掘。
YYLabel同樣封裝了設(shè)置富文本,排版,樣式等方法和屬性,同時(shí),還封裝了方法通過block來實(shí)現(xiàn)點(diǎn)擊事件。
- (void)yy_setTextHighlightRange:(NSRange)range
? ? ? ? ? ? ? ? ? ? ? ? ? color:(nullable UIColor *)color
? ? ? ? ? ? ? ? backgroundColor:(nullable UIColor *)backgroundColor
? ? ? ? ? ? ? ? ? ? ? ? userInfo:(nullable NSDictionary *)userInfo
? ? ? ? ? ? ? ? ? ? ? tapAction:(nullable YYTextAction)tapAction
? ? ? ? ? ? ? ? longPressAction:(nullable YYTextAction)longPressAction;
下面是使用YYLabel實(shí)現(xiàn)富文本展示的demo:
- (void)setupYYLabelText:(NSString *)plainText
{
? ? NSMutableAttributedString *attributeStr = [[NSMutableAttributedString alloc] initWithString: plainText];
? ? attributeStr.yy_font = [UIFont boldSystemFontOfSize:12];
? ? NSRange range1 = [plainText rangeOfString:@"www.baidu.com"];
? ? [attributeStr yy_setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle] range:range1];
? ? [attributeStr yy_setFont:[UIFont boldSystemFontOfSize:12] range:range1];
? ? UIColor *textColor1 = [UIColor redColor];
? ? UIColor *tapedBackgroundColor1 = [UIColor grayColor];
? ? [attributeStr yy_setTextHighlightRange:range1 color:textColor1 backgroundColor:tapedBackgroundColor1 tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
? ? ? ? //點(diǎn)擊跳轉(zhuǎn)鏈接
? ? ? ? NSString *link = @"www.baidu.com";
? ? ? ? NSURLComponents *components = [[NSURLComponents alloc] initWithString:link];
? ? ? ? if (components)
? ? ? ? {
? ? ? ? ? ? if (!components.scheme)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? components.scheme = @"http";
? ? ? ? ? ? }
? ? ? ? ? ? [[UIApplication sharedApplication] openURL:[components URL]];
? ? ? ? }
? ? }];
? ? NSRange range2 = [plainText rangeOfString:@"188XXXXXXXX"];
? ? [attributeStr yy_setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle] range:range2];
? ? [attributeStr yy_setFont:[UIFont boldSystemFontOfSize:12] range:range2];
? ? UIColor *textColor2 = [UIColor redColor];
? ? UIColor *tapedBackgroundColor2 = [UIColor grayColor];
? ? [attributeStr yy_setTextHighlightRange:range2 color:textColor2 backgroundColor:tapedBackgroundColor2 tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {//點(diǎn)擊提示是否要打電話
? ? ? ? NSString *phone = @"188XXXXXXXX";
? ? ? ? NSLog(@"%@", phone);
? ? ? ? NSMutableString * str=[[NSMutableString alloc] initWithFormat:@"tel:%@",phone];
? ? ? ? [[UIApplication sharedApplication] openURL:[NSURL URLWithString:str]];
? ? }];
? ? self.yyLabel.attributedText = attributeStr;
}
#pragma mark - getter/setter
- (YYLabel *)yyLabel
{
? ? if (!_yyLabel)
? ? {
? ? ? ? _yyLabel = [[YYLabel alloc] init];
? ? ? ? _yyLabel.frame = CGRectMake(20, 100, self.view.frame.size.width-40, 300);
? ? ? ? _yyLabel.textAlignment = NSTextAlignmentCenter;
? ? ? ? _yyLabel.textVerticalAlignment = YYTextVerticalAlignmentCenter;
? ? ? ? _yyLabel.numberOfLines = 0;
? ? ? ? _yyLabel.backgroundColor = [UIColor colorWithWhite:0.933 alpha:1.000];
? ? }
? ? return _yyLabel;
}
在我們展示消息列表時(shí),還需要自適應(yīng)的去計(jì)算YYLabel的高度,計(jì)算方法如下:
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
YYTextLayout *layout = [YYTextLayout layoutWithContainerSize:CGSizeMake(width - 81, CGFLOAT_MAX) text:attributeStr];
self.height = layout.textBoundingSize.height;
在開發(fā)過程中,用YYLabel來展示文本消息,但是在展示的過程中發(fā)現(xiàn),YYLabel的點(diǎn)擊事件并不響應(yīng),通過查找代碼打印日志,發(fā)現(xiàn)YYLabel在觸發(fā)觸摸事件的時(shí),按照touchesBegan->touchesMoved->touchesCancelled的順序走的代碼,但是點(diǎn)擊事件的響應(yīng)是在touchesEnded方法中。而發(fā)生這種情況的原因是YYLabel的父視圖有一個(gè)點(diǎn)擊手勢UITapGestureRecognizer,父視圖的手勢識(shí)別了點(diǎn)擊之后,子視圖的觸摸就會(huì)被取消。解決的辦法是:將父視圖的點(diǎn)擊手勢UITapGestureRecognizer的屬性cancelsTouchesInView設(shè)置為NO,這樣父視圖在識(shí)別了手勢之后,還會(huì)把點(diǎn)擊事件傳遞給其他視圖。