这周在做消息页面的文本消息展示时,遇到了选择什么样的控件去展示文本消息的问题。
在一个消息列表中,相比于图片,语音等类型的消息,文本消息是最常见的,但是文本消息的展示也是最复杂的。在文本消息中包含多种信息,比如链接,电话号码等等特殊的字符。不仅要在UI展示上对这些特殊字符进行处理,也就是富文本的展示,还要对其设置相应的点击事件。
UILabel和UITextView
对于常见的系统控件UILabel和UITextView,都可以实现富文本的展示。
区别在于:
1、UITextView有系统相应的代理方法可以对链接字符和电话字符处理相应的点击事件。而UILabel没有这样的功能。
2、UITextView和UILabel都可以实现自动换行。由于UILabel不可编辑,也不可滑动。而UITextView继承自UIScrollView,是可编辑的,也是可滑动。但是UITextView在展示时,距离上下左右都分别有8px的距离,因此使用计算字符串长度和高度的方法 [NSString sizeWithFont:constrainedToSize:lineBreakMode:]时,需要注意这些边距细节的加减。因此,UILabel在展示消息时可以更方便的计算自适应高度,而UITextView会比较麻烦一些。
M80AttributedLabel
这个自定义的控件,是这周在看网易云信的源码时学习到的。
M80AttributedLabel是一个继承自UIView的控件,实现了label的基本功能外,还封装了一些富文本展示的方法,可以自动的检测链接,设置链接的排版样式,以及相应的点击事件的代理方法。
在这里着重看了M80AttributedLabel如何实现点击事件的过程。
1、根据UIView的触摸代理方法获取到当前触摸点的位置坐标;
2、根据当前文本的frame,得到frame所对应的行数组( CFArrayRef)及每行的原点坐标数组;
3、遍历行数组,通过一系列矩阵变换和位置操作,取得当前点击区域的文字在整段文字中的索引;
4、在正则匹配到的URL数组中(该数组的每个元素存储了URL及其对应的位置),查找该索引所对应的URL;
5、找到对应的URL后,就会通过safari打开该链接。
下面是使用M80AttributedLabel的一个小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)
{
//默认添加 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
YYLabel也是一个继承自UIView的自定义控件,是YYKit下的一部分。据说YYKit很强大,但是目前用到的还只是其中的YYLabel,更强大的功能,下来再慢慢挖掘。
YYLabel同样封装了设置富文本,排版,样式等方法和属性,同时,还封装了方法通过block来实现点击事件。
- (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实现富文本展示的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) {
//点击跳转链接
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) {//点击提示是否要打电话
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;
}
在我们展示消息列表时,还需要自适应的去计算YYLabel的高度,计算方法如下:
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
YYTextLayout *layout = [YYTextLayout layoutWithContainerSize:CGSizeMake(width - 81, CGFLOAT_MAX) text:attributeStr];
self.height = layout.textBoundingSize.height;
在开发过程中,用YYLabel来展示文本消息,但是在展示的过程中发现,YYLabel的点击事件并不响应,通过查找代码打印日志,发现YYLabel在触发触摸事件的时,按照touchesBegan->touchesMoved->touchesCancelled的顺序走的代码,但是点击事件的响应是在touchesEnded方法中。而发生这种情况的原因是YYLabel的父视图有一个点击手势UITapGestureRecognizer,父视图的手势识别了点击之后,子视图的触摸就会被取消。解决的办法是:将父视图的点击手势UITapGestureRecognizer的属性cancelsTouchesInView设置为NO,这样父视图在识别了手势之后,还会把点击事件传递给其他视图。