概述
其实早都想写一篇关于评论回复功能的记录了,一直也没有时间,而且这块内容包含的东西还挺多,所以为了方便叙述,准备分两篇来记录这块内容。
功能
现在随着App的发展,各家的评论回复功能不尽相同,一般都是以单条的回复为主,如果想查看这条评论的回复内容,点开进入新的界面来展示评论回复内容。这是比较方便的设计方案,实现起来也相对逻辑清晰。
然后还有一种评论回复功能就相对复杂点了,有点类似于微信朋友圈的评论回复,所有的评论以及回复内容显示到当前的界面上以满足其评论内容的显示。
我今天想说的是第二种:类似于朋友圈的评论回复。
分析
以微信朋友圈为例,如下图:
主要以主评论与回复为主,回复在主评论下方,是全部展示出来的效果。看到这样的评论回复内容,首先关注的地方会在回复那个地儿,它到底是什么鬼?一条一条的展示出来,难不成是一个新的tableView
?
对于这样的内容界面,貌似有了两种处理方案:
- 使用
UITableView
进行回复的展示 - 使用
UILabel
进行回复的布局展示
实现
以上说了辣么多,重点还是在这,如何去实现它呢?貌似两种方案都可行呢!好吧,那就都实现一下咯?(篇幅原因,这次主要先介绍第一种实现)
-
使用
UITableView
进行回复的展示这样的效果貌似实现起来很舒服,因为tableView如果充当回复那块的内容的话,省了很多布局的琐事了,只需要对tableView进行内容高度更新既可,保证内容高度始终等于控件的frame,这样就不会出现一些奇奇怪怪的界面滚动问题了。先看看初期效果,如下:
以上回复的界面是通过嵌套tableView来实现的,效果貌似还不错,界面的层级也很简单,如下:
先看实现的代码,然后通过代码来说明实现过程。
外层cell实现
#import "LCCommentCell.h"
#import "Masonry.h"
#import "LCReplyCell.h"
static NSString *ReplyCellID = @"replyID";
@interface LCCommentCell ()
@property (nonatomic,strong) UILabel *cmtContentLabel; //这里的内容可以根据项目来进行配置
@property (nonatomic,strong) UITableView *replyView; //回复的界面
@end
@implementation LCCommentCell
- (void)awakeFromNib {
[super awakeFromNib];
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self setUI];
}
return self;
}
#pragma -mark- setter and getter
- (void)setReplyArray:(NSArray *)replyArray{
_replyArray = replyArray;
[self.replyView reloadData];
//强制刷新回复界面,然后更新回复区域高度
[self.replyView layoutIfNeeded];
[self.replyView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.replyView.contentSize.height);
}];
//获取当前的回复区域界面的高度
self.cellHeight = self.replyView.frame.origin.y + self.replyView.contentSize.height + 60;
NSLog(@"current height is %f",_cellHeight);
}
#pragma -mark- UI
- (void)setUI{
[self.contentView addSubview:self.cmtContentLabel];
[self.contentView addSubview:self.replyView];
[self.cmtContentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(10);
make.leading.mas_equalTo(10);
make.trailing.mas_equalTo(-10);
}];
[self.replyView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.cmtContentLabel.mas_bottom).with.offset(8);
make.leading.mas_equalTo(20);
make.trailing.mas_equalTo(-10);
}];
}
#pragma -mark- tableView delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(@"current count is %ld",self.replyArray.count);
return self.replyArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
LCReplyCell *cell = [tableView dequeueReusableCellWithIdentifier:ReplyCellID];
cell.cmtStr = self.replyArray[indexPath.row];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *replyStr = self.replyArray[indexPath.row];
CGRect bounds = [replyStr boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 20, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} context:nil];
CGFloat height = ceil(bounds.size.height);
return height;
}
#pragma -mark- lazy load
- (UILabel *)cmtContentLabel{
if (!_cmtContentLabel) {
_cmtContentLabel = [UILabel new];
_cmtContentLabel.text = @"大家好,我是新人,求罩";
_cmtContentLabel.numberOfLines = 0;
}
return _cmtContentLabel;
}
- (UITableView *)replyView{
if (!_replyView) {
_replyView = [UITableView new];
[_replyView registerClass:[LCReplyCell class] forCellReuseIdentifier:ReplyCellID];
_replyView.layer.cornerRadius = 2;
_replyView.layer.masksToBounds = YES;
_replyView.separatorStyle = UITableViewCellSeparatorStyleNone;
_replyView.scrollEnabled = NO;
_replyView.delegate = self;
_replyView.dataSource = self;
}
return _replyView;
}
@end
内层回复界面cell的实现
#import "LCReplyCell.h"
#import "Masonry.h"
#define RGB(R,G,B,A) [UIColor colorWithRed:R/255.0 green:G/255.0 blue:B/255.0 alpha:A]
@interface LCReplyCell ()
@property (nonatomic,strong) UILabel *replyLabel;
@end
@implementation LCReplyCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self setUI];
}
return self;
}
- (void)setUI{
[self.contentView addSubview:self.replyLabel];
[self.replyLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#pragma -mark- setter and getter
- (void)setCmtStr:(NSString *)cmtStr{
_cmtStr = cmtStr;
self.replyLabel.text = cmtStr;
}
#pragma -mark- lazy load
- (UILabel *)replyLabel{
if (!_replyLabel) {
_replyLabel = [UILabel new];
_replyLabel.backgroundColor = RGB(235, 235, 235, 1);
_replyLabel.font = [UIFont systemFontOfSize:14];
_replyLabel.numberOfLines = 0;
}
return _replyLabel;
}
@end
viewContrller中的实现使用
#import "ViewController.h"
#import "LCCommentCell.h"
#import "Masonry.h"
static NSString *CMTID = @"cmtID";
@interface ViewController ()
@property (nonatomic,strong) UITableView *tableView;
@property (nonatomic,strong) NSMutableArray *replyArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(20);
make.leading.trailing.bottom.mas_equalTo(0);
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma -mark- tableView delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
LCCommentCell *cell = [tableView dequeueReusableCellWithIdentifier:CMTID];
cell.replyArray = self.replyArray;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
LCCommentCell *cell = [[LCCommentCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CMTID];
cell.replyArray = self.replyArray;
return cell.cellHeight;
}
#pragma -mark- lazy load
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [UITableView new];
[_tableView registerClass:[LCCommentCell class] forCellReuseIdentifier:CMTID];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}
- (NSMutableArray *)replyArray{
if (!_replyArray) {
NSString *replyContent = @"陈慧回复吴东:简直了,神经病吧?";
_replyArray = [NSMutableArray new];
for (int i = 0; i<10; i++) {
[_replyArray addObject:replyContent];
}
}
return _replyArray;
}
@end
其中有几个技术点需要注意:
-
如何计算评论回复的高度以及获取高度的时机
可以这样分析,由于回复的控件是一个tableView,tableView所承载的是UILabel,这些label如果全部都填充完毕之后,那么对应的tableView的内容高度contentSize就是当前所要全部展示的内容,所以为了保证所有回复的内容全部展示完全,可以在更新回复界面的tableView之后更行它的frame的高度,让其frame的高度等于内容高度既可完成内容的展示,另外,为了保证滑动过程中显示回复的tableView界面没有拖动效果,可以禁用回复的tableView的bounce属性。
需要注意的是在更新回复的frame的高度的时候是在当前回复全部刷新完成之后,由于tableView的刷新的部分回调是异步进行的,所以可能获取高度不准确,为了解决这样的问题,可以使用强制刷新来同步界面的刷新完成,之后再去更新frame。 -
如何计算外层cell的高度以及获取高度的时机
这里比较有意思的地方在与评论的外层cell的高度计算,这里如果使用纯代码进行所有控件的高度的计算,不免过于麻烦,涉及到各个控件的frame布局的计算。这里介绍一种新的方法来进行cell的高度计算,当然也仅供参考。
可以这样去分析界面,如下图所示,实际上只要获取回复的tableView在布局以后的frame即可知道当前的cell高度,通过frame的y值加上当前的height再加底部的空白高度拿到了当前的cell 的高度。当然这是在cell布局之后才能获取。所以在计算cell的高度的时候我们可以借助这样一个思路进行。
所以在进行tableView的高度的回调的时候,我们可以创建一个临时的cell,将对应的数据传给当前cell,然后cell布局之后,去获取当前的cell的高度,这个高度可以写在当前的cell中,高度的获取可以放在界面回复reply之后进行,相关代码如下:
外层计算cell高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
LCCommentCell *cell = [[LCCommentCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CMTID];
cell.replyArray = self.replyArray;
return cell.cellHeight;
}
cell属性设置
- (void)setReplyArray:(NSArray *)replyArray{
_replyArray = replyArray;
[self.replyView reloadData];
//强制刷新回复界面,然后更新回复区域高度
[self.replyView layoutIfNeeded];
[self.replyView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.replyView.contentSize.height);
}];
//获取当前的回复区域界面的高度
self.cellHeight = self.replyView.frame.origin.y + self.replyView.contentSize.height + 60;
NSLog(@"current height is %f",_cellHeight);
}
这里需要说一点界面优化的内容,由于每次的高度获取都是实时的,所以针对于这一点内容,我们可以考虑将每次拿来的高度缓存起来,从而避免因高度的重复计算导致的性能问题。
小结
使用tableView进行回复布局省了很多事,包括数据内容的更新以及点击事件的处理,基本都使用了这样tableView自带的一些功能特性,可谓是物有所值。所以小伙伴,实现评论回复有思路了吗?以上仅供参考哟~~