一、概述
- iOS开发中,相信许多开发者都遇到过,类似于像
微信朋友圈的评论回复
功能的开发,难点莫过于Cell里面的子控件布局
、点击事件的回调
和评论回复的逻辑处理
。 - 笔者将通过
两种方法
来实现微信朋友圈评论回复
功能,也将通过一个仿优酷视频评论和回复的Demo
来实战一番,本文将通过利用UITableView
的段头+cell+段尾
来实现,希望能为广大开发者提供一点思路,少走一些弯路,填补一些细坑。 - iOS实现微信朋友圈评论回复功能(二)
- iOS 实现优酷视频的评论回复功能
二 、 效果图
效果图.gif
三、利用UITableView
的段头+cell+段尾
实现
-
页面分析
-
技术分析
- 列表展示数据流
- 列表通常利用
tableView
来实现。 -
tableview
实现又分为自动布局
和绝对布局
(即Frame布局
)
自动布局
:个人推荐利用Masonry+UITableView-FDTemplateLayoutCell的方案来实现自动布局。
Frame布局
: 事先计算出Cell
子控件的Frame以及Cell
的高度,存入ModelFrame
里面,虽然计算稍微复杂
,但是性能好,可控性强,易动画,扩展维护成本低
。
两种布局方式不是本文的重点,依旧个人喜好,笔者偏好是Frame布局
,本文案例也将采用这种方式。
- 列表通常利用
-
Cell
里面的昵称支持点击
跳转用户信息- 首先明确内容支持
attributedString(富文本)
而不是text(普通文本)
。 - 可以使用
UILabel
的attributedText
,或者采用第三方框架TYAttributedLabel和YYText,笔者在此采用的是YYText。
- 首先明确内容支持
3. 技术难点
-
Frame布局
计算复杂- 实际项目中,我们从服务器获取的数据,转换为数据模型(
Model
),对应的数据有些不能直接显示在视图(View
)上,需要二次处理。tableviewCel
l高度计算,是tableView
使用以及优化的重点对象,最好的方式莫过于高度缓存
,笔者这里为每个模型(Model
)配备一个模型尺寸(ModelFrame
),主要用来计算tableViewCell
的高度(cellHeight
),以及子控件的尺寸(frame
),而且保证模型(Model
)的纯净性
以及减少胖模型
的生成。 -
ModelFrame
模型持有Model
,并开放cell
中各个子控件的Frame
属性,,以及cellHeight
缓存cell
高度。注意:这里属性应该设置为readonly
来修饰更为合理,防止外界修改对应的尺寸(Frame
),切记:细节显能力。 - 重写
ModelFrame
的Model
的setter
方法,计算tabelViewCell
和headerView
子控件的Frame
以及缓存cell
的高度cellHeight
和headerView
的高度height
。 -
tableViewCell
和headerView
自身持有各自ModelFrame
模型,在tableView
的数据源给tableViewCellCell
和headerView
注入ModelFrame
时,确定tabelViewCell
和headerView
的子控件的布局(Frame
)以及数据。
- 实际项目中,我们从服务器获取的数据,转换为数据模型(
-
Cell
宽度的修改
只要重写tableViewCell
的frame
的setter
方法即可统一修改cell
的尺寸。- (void)setFrame:(CGRect)frame { frame.origin.x = MHVideoTopicAvatarWH+2*MHVideoTopicHorizontalSpace; frame.size.width = MHMainScreenWidth - frame.origin.x - MHVideoTopicHorizontalSpace; [super setFrame:frame]; }
-
复杂的事件回调
事件传递三种方式delegate
,notification
,block
。笔者项目采用的是代理delegate
,将所有事件代理给viewController
,事件的传递详情,请查看文章下方给出的Demo地址。
四、细节处理
- 设置文本的额外区域,防止文字过少,用户无法点中文本的
bug
。
Label的额外区域@2x.png
// 文本
YYLabel *contentLabel = [[YYLabel alloc] init];
// 设置文本的额外区域,修复用户点击文本没有效果
UIEdgeInsets textContainerInset = contentLabel.textContainerInset;
textContainerInset.top = MHVideoTopicVerticalSpace;
textContainerInset.bottom = MHVideoTopicVerticalSpace;
contentLabel.textContainerInset = textContainerInset;
contentLabel.numberOfLines = 0 ;
contentLabel.textAlignment = NSTextAlignmentLeft;
[self.contentView addSubview:contentLabel];
- 点击
评论昵称
获取用户模型(MHUser
)
- 点击
评论昵称
,在评论模型(MHComment
)中通过通知传递用户模型(MHUser
)。虽获取用户模型(MHUser
)简单,但使用通知
会增加项目的耦合性
。
// 文本高亮模型
YYTextHighlight *toUserHighlight = [YYTextHighlight highlightWithBackgroundColor:[UIColor colorWithWhite:0.000 alpha:0.220]];
// 这里痛过属性的userInfo保存User模型,后期通过获取模型然后获取User模型
toUserHighlight.userInfo = @{MHCommentUserKey:self.toUser};
// 点击用户的昵称的事件传递
// toUserHighlight.tapAction = ^(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect)
// {
// // 这里通过通知把用户的模型传递出去
// };
- 点击
评论昵称
,通过label
的点击事件来获取,由于YYLable
的highlightTapAction
的事件,无法获取YYTextHighlight
模型,但是通过KVC
可以获取到YYTextHighlight
模型,从而得到textHighlight.userInfo
。这样一来通过delegate
就可将用户模型(MHUser
)传递出去。
__weak typeof(self) weakSelf = self;
contentLabel.highlightTapAction = ^(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect) {
// 利用KVC获取UserInfo 其实可以在MHComment模型里面利用 通知告知控制器哪个用户被点击了
YYTextHighlight *highlight = [containerView valueForKeyPath:@"_highlight"];
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(commentCell:didClickedUser:)]) {
[weakSelf.delegate commentCell:weakSelf didClickedUser:highlight.userInfo[MHCommentUserKey]];
}
};
五、期待
- 文章若对您有点帮助,请给个喜欢❤️,毕竟码字不易;若对您没啥帮助,请给点建议💗,切记学无止境。
- 针对文章所述内容,阅读期间任何疑问;请在文章底部评论指出,我会火速解决和修正问题。
- GitHub地址:https://github.com/CoderMikeHe
网友评论
这整个一块(段头 + cell + 段尾)是一个大的cell吧,然后里面的小cell应该是又是一个tableView列表,但这样的话不就是方案二吗(cell 里面嵌套tableView),所以猜测这整个一块难道就是一个tableView,然后内容就是组头+内容+组尾的方式,那这样的话这一个列表得多少个tableView,糊涂了,请教下大神
段(组)头:通过 `- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section`获取
段(组)尾巴:通过 `- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section`获取
Cell : 通过 `- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath` 获取
这能明白吗???
麻烦给我发一下好吗,谢谢了
cd /Users/lijie/Desktop/下载在/MHDevelopExample_Objective_C/MHDevelopExample
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.4.sdk
export TOOLCHAINS=
/Applications/Xcode.app/Contents/Developer/usr/bin/copypng -compress -strip-PNG-text /Users/lijie/XNB/Resource/Architecture/Default/cmh_default_buy_goods@3x.png /Users/lijie/Library/Developer/Xcode/DerivedData/MHDevelopExample-cgjniftsqgccnearzdpjeifgtvpi/Build/Products/Debug-iphonesimulator/MHDevelopExample.app/cmh_default_buy_goods@3x.png
ERROR: Can't find /Users/lijie/XNB/Resource/Architecture/Default/cmh_default_buy_goods@3x.png
Command /Applications/Xcode.app/Contents/Developer/usr/bin/copypng failed with exit code 1 下载后遇到这样的错误,两种方法都试过了
[!] Error installing CHTCollectionViewWaterfallLayout
[!] /usr/bin/git clone https://github.com/chiahsien/CHTCollectionViewWaterfallLayout.git /var/folders/wp/pfk_75q917dc0rjsrbbj7b4w0000gn/T/d20180403-1060-xcbw1 --template= --single-branch --depth 1 --branch 0.9.6
Cloning into '/var/folders/wp/pfk_75q917dc0rjsrbbj7b4w0000gn/T/d20180403-1060-xcbw1'...
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: unpack-objects failed
https://github.com/gsdios/GSD_WeiXin
好多库啊 0.0。已经迫不及待了
最近也在做类似的项目。希望有帮助
2.自定义UITableView,在这里面通过touchBegain来判断点击的view和point,当点击的view为HeaderfooterView则忽略,如果点击的View为当前自定义的UITableView,那么修改point的x值,使x位于cell的frame内,然后通过indexPathForRowAtPoint获取到点击的indexPath,通过协议返回到ViewController中
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch.view isKindOfClass:[self class]]) {
CGPoint originalLocation = [touch locationInView:self];
originalLocation.x = CGRectGetWidth(self.frame) / 2.0;//此处需要根据实际情况调整,确保x的值在cell的frame内
NSIndexPath *indexPath = [self indexPathForRowAtPoint:originalLocation];
if (self.ksDelegate && [self.ksDelegate respondsToSelector:@selector(tableView:clickOutOfCellAtIndexPath:)]) {
[self.ksDelegate tableView:self clickOutOfCellAtIndexPath:indexPath];
}
}
[super touchesBegan:touches withEvent:event];
}
推荐使用第二种方法,感觉比较完美
但是,我们程序猿也得强硬一点,用户点击左侧没响应,会乖乖去点击cell。