该篇文章主要介绍一个实现聊天界面的思路过程,源码可以在 源码链接 获得,该工程实现聊天的基本功能,功能还不够完善,欢迎大家提 PR ,效果图如下所示
我希望通过相对简单的方式实现界面的布局,没有复杂的计算达到自适应的效果。
iOS8 新功能介绍
虽然 self size cell 最终没有在我的工程中用到,但是这是我曾经挖过的坑,所以在此做了简单的介绍。
在 iOS 8 中, UITableView 新增一项功能 self size cells ,这是一项通过 UITableViewCell 的约束自动自动计算 UITableView contentSize 的技术。这个新特性给我们带来两个个好处。
我们不再需要为了自适应文字,去计算每个 cell 中文字所需要的高度。
而且有更高的性能。(因为 UITableView 每次reloadData 的时候都会重新计算 cell 的高度,意味着如果有一万个 cell 要展示,需要 调用heightForRowAtIndexPath 一万次,这效率是特别低的)
我们通过一个简单的 demo 来介绍一下 Self Size Cells 的用法,demo 源码 效果如图
使用步骤是
为 UITableViewCell 添加约束
设置 UITableView 的 estimatedRowHeight 属性
设置rowHeight 为 UITableViewAutomaticDimension
有一点需要注意,代码中不能实现heightForRowAtIndexPath 这个方法 添加约束,有一个原则是,除了自适应 text的高度不需要约束外,需要确定所有必要约束
用代码来说就是(这里用到第三方库 SnapKit 做代码约束 SnapKit 传送门)
textview.snp_makeConstraints{ (make)in
make.top.equalTo(self.contentView).offset(15)
make.width.equalTo(100)
make.left.equalTo(self.contentView).offset(15)
make.bottom.equalTo(self.contentView).offset(-15)
}
对于 UILabel 来说还需要把 numberOfLines 置为 0 然后设置 UITableView 的必要属性
messageTable.estimatedRowHeight=44
messageTable.rowHeight=UITableViewAutomaticDimension
以上便是,使用 self size cell 的所有步骤。
实战篇
接下来便是实战部分,我希望在聊天页面中使用 self size cell 这个功能,聊天页面的效果图
以下下是我为 messageCell 制作约束图,事实上用的是代码约束,详情可以查看我的源码
问题
如果我想实现 一个功能像微信一样下拉刷新,而且消息停留在原来的消息页面上,如下图所示
分析:在刷新数据后 调用 tableview.reloadData 方法,可以刷新 tableview 显示的数据,不过 tableview 会滚动到最顶部。幸运的是 tableView 是 UIScrollView 的子类,如果我们改变了内容, contentSize 这个属性一定会改变,也就是说系统一定会掉用 contentSize 的 set 方法。如果我们重新这个 set 方法,在每次掉用 setContentSize 的时候计算出之前视窗所在的位置,并且在设置完 contentSize 后移动到计算号的位置,就能平滑的上拉加载更多的历史消息了。
下面是实现平滑滚动的关键代码
@objc(JChatMessageTable)
class JChatMessageTable: UITableView {
var isFlashToLoad:Bool! = false
override var contentSize: CGSize {
didSet {
if self.isFlashToLoad != false {
if !CGSizeEqualToSize(self.contentSize, CGSizeZero) {
if oldValue.height < self.contentSize.height {
var offset = self.contentOffset
offset.y = self.contentSize.height - oldValue.height
self.contentOffset = offset
}
}
}
self.isFlashToLoad = false
}
}
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadMoreMessage() {
self.isFlashToLoad = true
self.reloadData()
}
}
想法是好的,但是现实总是残酷的,在具体实行的时候出现了一个新的问题,因为我用的是 Self size cell 来自适应 UITableView 的 contentSize 的, Self size cell 在处理 UITableView 的 contentSize 时候并不是一次性赋值成功的,它是每 5 个点的增加 height(contentSize.height),直到合适的高度位置, 也就是说 contentSize 的 Set 方法会被掉用很多次,而且上面的代码完全没有作用(可以想象结果是只有最后一次掉用 contentSize 的 Set 方法起到了作用小于 5 个点的偏移量,由于这个变化真的很小,我也是在这个地方被坑了一次),由于 Self size cells 的这个特性,似乎很难实现这个功能。
此时我果断放弃了使用 Self Size Cells ,虽然比较心痛,不过我们大致了解了 Self size cell 是如何自适应高度的。 self size cell 主要给我们带来两个好处
能够得到更高的执行效率
我们不需要为文字的 frame 进行复杂的计算 ,只需要设置 width 的约束和位置,就能够得到自适应的
Size
由于 messageTable 消息的展示是通过分页加载消息的方式,第一次只会添加 20 条的消息,也就不会出现调用 heightForRowAtIndexPath 时间过长的卡顿问题。但是如果用户不断的下拉刷新 heightForRowAtIndexPath 的执行时间也会出现线性的增加,所以为了减少这方面的时间开支,我们在每次成功加载一个新的 cell 的时候把高度缓存起来,这样就可以减少计算的时间,每次只需要计算新展现的历史消息高度就可以了。
第一个问题经过分析我们可以通过缓存高度的方式提高性能.
对于第二点,也是最重要的一点,如何不通过手动计算获得 UITableViewCell 所需要的高度,经过分析发现 UITableViewCell 有一个方法 systemLayoutSizeFittingSize 可以计算返回自身的高度,所以我接下来生成一个全局的 UITableViewCell 用于计算UITableViewCell 的高度这样我们就不需要手动计算 Cell 的高度了。通过
返回的高度就是 UITableView 所需要的高度。
自适应输入框
接下来,我们需要实现如下图的效果,输入框能够自适应输入文字的大小 JChatInputView
我们需要输入框能够自适应文本的大小,我们
需要给 TextView 添加如下约束
inputTextView?.snp_makeConstraints(closure: { (make) ->Voidin
make.right.equalTo(self.showMoreBtn.snp_left).offset(-5)
make.left.equalTo(self.switchBtn.snp_right).offset(5)
make.top.equalTo(inputWrapView).offset(5)
make.bottom.equalTo(inputWrapView).offset(-5)
make.height.greaterThanOrEqualTo(30)
make.height.lessThanOrEqualTo(100)
})
inputWarpView 添加如下约束。不需要添加高度约束, 因为 inputWarpView 的高度由 TextView 的高度和其他约束计算得出
inputTextView?.snp_makeConstraints(closure: { (make) ->Voidin
make.right.bottom.left.equalTo(superView)
})
到此介绍了一个聊天界面的自适应 UI 部分,灵活的使用约束可以减少大量的代码。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。