这是我参与更文挑战的第26天,活动详情查看: 更文挑战
iOS一个典型面经
在iOS开发中,经常会被问的一个面试题就是如何优化TableView?
这个问题可以算是面试造火箭,因为你的Cell承载的内容级别不同,需要优化着手的地方也就不同,面试的时候,只能泛泛而聊,而不能具体到细节。
有关如何优化TableView,网上文章一堆了,我就不在大佬面前班门弄斧了。
但是这次玩安卓App上的Cell,还是有些内容能够聊一下的。
分析页面
上面的这个图就是首页的Cell的一些形式:
-
Cell可能有图片,可能没有图片。Info模型中envelopePic可能有值,可能没有值。
-
Cell的高度不确定。Info模型中title的字符串长度不确定
-
作者名称可能有或者没有。Info模型中author字段可能有值,可能没有值。
-
赞称可能有或者没有。Info模型中zan字段可能有值,可能没有值,并且在zan的数量大于0时才显示。
从上面的分析可以看出,带有image,右侧有title,而title下方有author,zan和author同高,并且author与zan距离底部的间距一致,这是该Cell的基本布局。
在赋值模型的时候,根据envelopePic字段是否有值来确定image是否隐藏,右侧的title、author、zan是否随之改变布局。
难点就是如何让高度可以变化,这一点,我们在代码中体现。
好了上面分析完了,上代码,里面有详细注释,请注意查看:
import UIKit
class InfoViewCell: UITableViewCell {
/// info模型的set与get
var info: Info! {
/// set方法
set {
_info = newValue
/// 拿出titile
var title = newValue.title
/// 去掉title中的html标签,赋值给contentLabel
contentLabel.text = title?.filterHTML()
/// 赋值authorLabel
authorLabel.text = newValue.author
/// 判断zan有值,而且大于0,赋值给praiseLabel
if let zan = newValue.zan, zan > 0 {
praiseLabel.text = "赞: \(zan)"
}
/// 重点
/// 判断envelopePic是否有值
if let imageString = info.envelopePic, let url = URL(string: imageString) {
/// envelopePic有值,不隐藏picView
picView.isHidden = false
/// 使用Kingfisher进行图片加载
picView.kf.setImage(with: url, placeholder: R.image.saber())
/// 重置布局为初始布局,由于Cell会被复用,如果重置,直接用,可能从复用池捞出来的是改变了布局的Cell,显示会有异常
contentLabel.snp.remakeConstraints { make in
make.leading.equalTo(picView.snp.trailing).offset(16)
make.trailing.equalTo(contentView).offset(-16)
make.top.equalTo(contentView).offset(10)
}
authorLabel.snp.remakeConstraints { make in
make.leading.equalTo(contentLabel)
make.top.equalTo(contentLabel.snp.bottom).offset(10)
make.bottom.equalTo(contentView.snp.bottom).offset(-10)
}
}else {
/// envelopePic没有值,隐藏picView
picView.isHidden = true
/// contentLabel和authorLabel布局向左偏移,重新布局
contentLabel.snp.remakeConstraints { make in
make.leading.equalTo(contentView).offset(16)
make.trailing.equalTo(contentView).offset(-16)
make.top.equalTo(contentView).offset(10)
}
authorLabel.snp.remakeConstraints { make in
make.leading.equalTo(contentLabel)
make.top.equalTo(contentLabel.snp.bottom).offset(10)
make.bottom.equalTo(contentView.snp.bottom).offset(-10)
}
}
} get {
/// get方法
return _info
}
}
/// 有点像Dart中的对私有变量以_开头,哈哈,其实在OC时代,Model的set方法不是这样的
private var _info: Info!
/// 懒加载图片
private lazy var picView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
/// 懒加载内容Label
private lazy var contentLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 15)
label.textColor = .black
return label
}()
/// 懒加载作者label
private lazy var authorLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 11)
label.textColor = .gray
return label
}()
/// 懒加载点赞label
private lazy var praiseLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 11)
label.textColor = .gray
return label
}()
/// 初始化方法中进行布局
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 布局方法细化
private func setupUI() {
/// 右侧有箭头
accessoryType = .disclosureIndicator
/// 添加图片
contentView.addSubview(picView)
picView.snp.makeConstraints { make in
/// 距Cell左侧16
make.leading.equalTo(contentView).offset(16)
/// 居Cell的Y轴正中
make.centerY.equalTo(contentView)
/// 图片宽高44
make.width.height.equalTo(44)
}
/// 添加内容
contentView.addSubview(contentLabel)
contentLabel.snp.makeConstraints { make in
/// label的左边距图片的右侧16
make.leading.equalTo(picView.snp.trailing).offset(16)
/// label的右边距Cell的右侧16
make.trailing.equalTo(contentView).offset(-16)
/// label的顶部距Cell的顶部10
make.top.equalTo(contentView).offset(10)
}
/// 添加点赞
contentView.addSubview(praiseLabel)
praiseLabel.snp.makeConstraints { make in
/// 右侧等于内容的右侧,即label的右边距Cell的右侧16
make.trailing.equalTo(contentLabel)
/// 顶部距离contentLabel的底部10
make.top.equalTo(contentLabel.snp.bottom).offset(10)
/// 底部距离Cell的底部向上偏移10,这一行非常关键,是用来撑满Cell使得高度动态化的关键
make.bottom.equalTo(contentView.snp.bottom).offset(-10)
}
/// 添加作者
contentView.addSubview(authorLabel)
authorLabel.snp.makeConstraints { make in
/// 左侧等于内容的左侧,即距Cell左侧16
make.leading.equalTo(contentLabel)
/// 顶部与底部参照点赞
make.top.bottom.equalTo(praiseLabel)
}
}
}
复制代码
因为上面分析中已经非常详细的讲解了布局思路,所以这里只说如何撑满整个Cell,不知道大家对于我之前的文章——Swift:布局库——SnapKit|周末学习最后一段SnapKit与UIScrollView还有没有印象?
没印象的话快去读一读,撑满Cell的秘诀就是布局在Cell中的最后一个subView,使用它的bottom与Cell的contentView去做约束,它决定了Cell的动态高度!也就这段代码中最后一段:make.bottom.equalTo(contentView.snp.bottom).offset(-10)
:
/// 添加点赞
contentView.addSubview(praiseLabel)
praiseLabel.snp.makeConstraints { make in
/// 右侧等于内容的右侧,即label的右边距Cell的右侧16
make.trailing.equalTo(contentLabel)
/// 顶部距离contentLabel的底部10
make.top.equalTo(contentLabel.snp.bottom).offset(10)
/// 底部距离Cell的底部向上偏移10,这一行非常关键,是用来撑满Cell使得高度动态化的关键
make.bottom.equalTo(contentView.snp.bottom).offset(-10)
}
复制代码
需要注意的是,在Cell中添加subView,最后是添加到Cell的contentView上,直接加到Cell当然没有问题,但是在本例子有系统的右侧箭头,或者侧滑的时,是会有异常的!
引用一段:
1、cell.addSubview
2、cell.contentView.addSubview
区别在于进行cell编辑时,比如cell内容向左移或者右移时,第一种方式子视图不会移动,第二可以,所以这种情况一般使用第二种方式。
还有在设置backgroundColor时,使用cell时左移或者右移颜色是不会变的,而用cell.contentView时,移动后的空白会显示cell的默认颜色,这种情况视实际情况选择。
总结:cell.contentView添加子控件的时候,相当于直接往cell上方添加子控件,独立于cell的存在的。而cell添加子控件相当于往cell上添加,跟cell是一体的。
另外,请不要尝试用UITableViewCell本身自带的textLabel、detailTextLabel、imageView通过SnapKit改变布局来使得Cell能够撑满Cell,我试了,不能成功。
最后我也有一个疑问,有大佬能指导一下我就好了:
在info的set方法中
,我使用的remakeConstraints
去更新约束,我之前使用的updateConstraints
,但是很遗憾崩溃了,如果有知道为啥,可否告诉我一下?
总结
本篇主要讲解了首页列表中的InfoViewCell的布局和其中的小技巧与注意事项。
通过前三天的文章(编写HomeViewModel、编写基类控制器、编写首页)以及本篇,我们基本上完成了玩安卓App的首页编写,这个页面的编写完成,基本上可以说整个玩安卓App的页面开发思路基本完成。
大家沿着这个思路走,可以逐步完成其他页面的编写。
明日继续
首页编写完了,我准备把登录、用户相关的账号模块拿出来讲一下。
因为玩安卓App,有几个接口设计必须登录成功后才能拿到数据。
大家加油!