上篇我们已经封装好了键盘,接下来就来实现信息流界面。很容易看出,信息流界面是一个tableView
,里面的每条消息则是一个个cell
;因为消息分纯文本、图片、视频、语音、文件等几类,怎么实现这些cell
;笔者在网上查看了一些优秀的实现,分别为以下几种:
1、使用CoreText
来绘制cell,有VVeboTableView
2、使用Autolayout
自动计算高度,有FDTemplateLayoutCell
笔者参考了大神ibireme的iOS 保持界面流畅的技巧中的思想,为每种消息创建一个cellLayout
用来计算相关控件的frame
,并且这些cellLayout
继承于相同的父类LXChatLayout
,该父类
包含了头像
、名字
、发送中
、背景
、发送失败
等控件的frame
和message
的值。为每种消息创建一个tableViewCell
,这些cell继承
于父类LXChatCell
。该父类有layout
、头像
、名字
、发送中
、发送失败
、cell的背景等属性
,还有处理相关事件的功能。通过先获取数据,然后计算相关控件的布局并缓存到内存中,再显示到tableView上,使用YYFPSLabel
来检测拖动tableView时的Fps基本在50以上。
talk is cheap,show code
//LXChatLayout.h
@interface LXChatLayout : NSObject {
UIEdgeInsets rightBubbleInset; // 右边cell背景的间距
UIEdgeInsets leftBubbleInset; // 左边cell背景的间距
CGFloat hInset; // 头像与tableView的间距
CGFloat marginHead; // 名字与头像的间距
CGFloat topInset; // cell的顶部间距
}
@property (nonatomic, strong) LXMessage *message;
@property (nonatomic, assign) CGRect headRect;
@property (nonatomic, assign) CGRect nameRect;
@property (nonatomic, assign) CGRect indicatorRect;
@property (nonatomic, assign) CGRect bgImgRect;
@property (nonatomic, assign) CGRect failRect;
@property (nonatomic, assign) CGFloat cacheHeight;
+ (instancetype)layoutWithMessage:(LXMessage *)msg;
- (BOOL)isMedia;
#pragma mark - rewrite methods
- (void)computeMineBy:(LXMessage *)message;
- (void)computeOtherBy:(LXMessage *)message;
@end
// LXChatCell.h
typedef void(^LXChatTapContentHandle)(LXChatLayout *layout, CGRect fileRect);
@interface LXChatCell : UITableViewCell
@property (nonatomic, strong) LXChatLayout *layout;
@property (nonatomic, strong) UIImageView *headImgView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UIActivityIndicatorView *indicatorView;
@property (nonatomic, strong) UIImageView *failImgView;
@property (nonatomic, strong) UIImageView *bgImgView;
@property (nonatomic, copy) LXChatTapContentHandle tapHandle;
- (void)tapContent:(UITapGestureRecognizer *)gesture;
@end
以上是父类的声明,下面将以纯文本消息为例来实现一个纯文本消息的cell及其cellLayout,其它的消息则参照这种方式即可实现。其关键代码如下
// LXChatTextLayout.h
@interface LXChatTextLayout : LXChatLayout
@property (nonatomic, assign) CGRect textRect;
@property (nonatomic, strong) NSMutableAttributedString *attribute;
@property (nonatomic, strong) YYTextLayout *textLayout;
@end
@implementation LXChatTextLayout
- (void)setMessage:(LXMessage *)message {
[super setMessage:message];
// 计算attributeString
....
}
- (void)computeMineBy:(LXMessage *)message {
[super computeMineBy:message];
....
// 计算出文本、背景气泡的frame以及 cell的高度
_textRect = CGRectMake(bgX + contentInset.left, bgY + insetTop, textWidth, textHeight);
self.bgImgRect = CGRectMake(bgX, bgY, textWidth + contentInset.left + contentInset.right, bgHeight);
self.cacheHeight += CGRectGetMaxY(self.bgImgRect);
}
- (void)computeOtherBy:(LXMessage *)message {
[super computeOtherBy:message];
....
// 计算出文本、背景气泡的frame以及 cell的高度
_textRect = CGRectMake(bgX + contentInset.left, bgY + insetTop, textWidth, textHeight);
self.bgImgRect = CGRectMake(bgX, bgY, textWidth + contentInset.left + contentInset.right, bgHeight);
self.cacheHeight += CGRectGetMaxY(self.bgImgRect);
}
// LXChatTextCell.h
@interface LXChatTextCell : LXChatCell
@property (nonatomic, strong) YYLabel *contentLabel;
@end
@implementation LXChatTextCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
_contentLabel = [[YYLabel alloc] init];
//_contentLabel.backgroundColor = [UIColor orangeColor];
[self.contentView addSubview:_contentLabel];
}
return self;
}
- (void)setLayout:(LXChatLayout *)layout {
[super setLayout:layout];
LXChatTextLayout *tLayout = (LXChatTextLayout *)layout;
self.contentLabel.textLayout = tLayout.textLayout;
[CATransaction begin]; // 关闭更改frame的隐式动画
[CATransaction setDisableActions:true];
self.contentLabel.frame = tLayout.textRect;
[CATransaction commit];
}
@end
在这里显示纯文本使用的是YYLabel
,是YYText
库中的一部分,功能非常强大,也支持异步绘制。在测试的过程中发现,如果快速拖动
tableView,更改
控件的frame
时,会看到有一个动画
的过程,这种体验很差;后来查找资料说是因为更改layer
的frame
产生隐式动画导致的,使用以下方法可以关闭该隐式动画。
[CATransaction begin];
[CATransaction setDisableActions:true];
// 更改frame
....
[CATransaction commit];
完成后效果如下