关于ZJTableViewManager
强大的数据驱动TableView,构建复杂TableView从未如此轻松。
使用
直接拖入 ZJTableViewManager 文件夹中的文件,或者使用 cocoapods:pod 'ZJTableViewManager', '~> 1.0.7'
适配
版本 | Swift | Xcode |
---|---|---|
0.2.7 | 4.0 / 4.2 | Xcode 10 或更高版本 |
1.0.3 或更高版本 | 4.0 ~ 5.2 | Xcode 10.2 或更高版本 |
简介
ZJTableViewManager 基于“数据驱动页面”的理念,接管了 UITableView
的 delegate
和 dataSource
逻辑,开发者只需关注数据处理,避免冗长的判断,使代码更易于维护。
例如,一个页面中有一个 UITableView
,包含5种不同的 Cell
。按照传统写法,在 tableView(_:cellForRowAt:)
代理方法中,会是这样:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if condition1 {
return SimpleStringCell
}else if condition2 {
return FullLengthTextFieldCell
}else if condition3 {
return TextCell
}else if condition4 {
return PasswordCell
}else if condition5 {
return SwitchCell
}else {
return DefaultCell
}
}
在下面两个方法中也可能需要写上这一系列判断条件。明显的缺点是代码冗长。
tableView(_:, heightForRowAt:)
tableView(_:, didSelectRowAt:)
在实际情况中,很多人直接使用 IndexPath
作为判断条件,大量使用 if else
。当需要对 Cell
显示顺序进行调整时,基于 IndexPath
的判断就会出现问题,修改起来特别容易出现错误。
当然,有经验的程序员会抽象出一个 type
,通过 type
来判断 Cell
类型,以避免 IndexPath
的缺陷。这其实已经算是一种数据驱动思想的体现,相比于使用 IndexPath
判断,更不容易出错。但这也还不够,在这些方法中仍然会有很多 if else
,这既影响观感,也影响逻辑理解。
因此,ZJTableViewManager 在此基础上做了进一步的封装,效果如下:
就像示例代码所示,不需要处理TableView的delegate和dataSource,不需要那些 if else
,Item控制Cell处理Cell的事件,我们只需用代码描述TableView的外观,它就会按照我们的描述构建出来。
使用方式
1.系统默认Cell
创建系统默认的cell,使用ZJTableViewItem类,创建之后加入section即可
let item = ZJTableViewItem(title: "测试cell 1")
section.add(item: item)
根据需要可以修改样式为subtitle
item.style = .subtitle
item.detailLabelText = "detail label text"
总结一下系统默认Cell的使用步骤:
- 页面上创建一个TableView(StoryBoard拖或者纯代码创建都可以)
- 通过这个TableView初始化一个manager
- 创建一个Section,加入到manager里
- 创建Cell对应的Item,赋值之后加入到section里
manager.reload()
具体不展开说了,系统cell就那几个样式,平时也很少用到,自己尝试吧。
2.自定义Cell
自定义Cell才是我们实际项目中用到最多的,所以这一块需要详细说一下。 我们来尝试自定义这样一个Cell
左边是一个UILabel,右边一个UISwitch,功能是在UISwitch开关时会发出回调,在VC中处理。
首先,新建一个ZJSwitchCell类,继承自UITableViewCell,勾选上Also create XIB file(当然不用XIB,纯代码布局也可以)
在xib文件里面拖上控件,并且把控件和UISwitch的value change事件拖线到Cell文件里面
下面是重点: 在ZJSwitchCell.swift文件里面写一个ZJSwitchCellItem类,继承自ZJTableViewItem,有三个属性,标题title,开关状态isOn,回调闭包didChanged。
让ZJSwitchCell遵循ZJCellProtocol协议,如图所示,Xcode会弹出提示,点击fix,会自动加上需要的方法和类型
ZJCelltemClass这里填写上前面写好的ZJSwitchCellItem类名
然后Xcode还会有个错误提示,继续点fix,就好了。
可能有时候Xcode自动fix补全的代码有问题,比如说
typealias ZJCelltemClass =
出现两遍或者根本没有fix按钮,不要慌,cmd+b编译一下,再试试就好了
然后在cellWillAppear()
方法里面写上赋值操作,它等价于tableView(_:, cellForRowAt:)
方法。再到valueChanged(:)
方法里面,记录UISwitch的状态,并把当前这个item通过回调传出去。Cell部分的自定义就完成了。
最后,在VC里面使用,使用之前需要manager.register(ZJSwitchCell.self, ZJSwitchItem.self)
注册一下,这和之前使用系统默认的Cell有区别,自定义的Cell都需要注册一下才可以使用。
class FormViewController: UIViewController {
var tableView: UITableView!
var manager: ZJTableViewManager!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
view.addSubview(tableView)
manager = ZJTableViewManager(tableView: tableView)
manager.register(ZJSwitchCell.self, ZJSwitchItem.self)
let section = ZJTableViewSection()
manager.add(section: section)
// Switch Item
section.add(item: ZJSwitchItem(title: "Switch Item", isOn: false, didChanged: { item in
zj_log(item.isOn)
}))
manager.reload()
}
}
总结一下自定义Cell的步骤:
- 新建Cell(XIB或者纯代码都可以)
- 创建Cell对应的Item,通过Item给Cell传值(实际项目中一般是用Item持有Model,在
cellWillAppear()
中通过item.model取值并赋值到控件里面) - 在VC中将Cell向TableViewManager注册。
其他使用方式请参考上面系统默认Cell的使用。
3.Cell固定高度及动态计算高度处理
前面示例中的两种Cell高度都是系统默认的44,但在实际项目中我们可能需要不同高度的Cell,如何处理呢?
固定高度:聪明的同学可能已经发现了,Item控制了Cell的所有表现,所以肯定是通过Item来控制的。Item中有一个cellHeight属性,给它赋值就可以控制Cell的高度。我们可以在重写Item对象的init()方法时,给它一个固定的高度
override init() {
super.init()
cellHeight = 100
}
或者在VC中初始化Item之后,再给cellHeight赋值,都是可以的。
动态高度:动态高度的前提是使用AutoLayout布局,确保约束没有缺失,然后在Item赋值之后,调用一下autoHeight(:)方法,高度就算好了。
let item = AutomaticHeightCellItem()
item.feed = feed
//计算高度
item.autoHeight(manager)
//把cell加入进section
section.add(item: item)
具体可以参考下面的文章,里面阐述得更加详细,这里就不展开了。 Swift UITableViewCell高性能动态计算高度
4.TableView相关事件(如点击事件)
设置点击事件回调:
item.setSelectionHandler { (callBackItem: LevelCellItem) in
//Do some thing
}
其他事件同理,包括section的一些事件(比如section即将出现之类的回调),具体请看Demo。
5.Scroll事件的代理
在使用TableView的同时,有时还需要处理Scroll事件,例如判断滚动是否停止,或监听滚动事件等,可以通过设置manager.scrollDelegate = self
并遵循ZJTableViewScrollDelegate
来获取所有滚动事件的回调,使用方式和UIScrollViewDelegate
相同。
Demo:
电商项目的评价、打星评价、添加评论图片
这里主要包含3个cell,一个是打星cell,一个是评论cell,还有一个是添加图片cell。在viewController中只有20行代码,耦合度低。
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Demo"
self.manager = ZJTableViewManager(tableView: self.tableView)
//register cell
self.manager?.register(OrderEvaluateCell.self, OrderEvaluateItem.self)
self.manager?.register(ZJPictureTableCell.self, ZJPictureTableItem.self)
//add section
let section = ZJTableViewSection(headerHeight: 10, color: UIColor.init(white: 0.9, alpha: 1))
self.manager?.add(section: section)
//add cells
for i in 0...10 {
i //评价cell
section.add(item: OrderEvaluateItem(title: "评价"))
let textItem = ZJTextItem(text: nil, placeHolder: "请在此输入您的评价~", ddChanged: nil)
textItem.isHideSeparator = true
section.add(item: textItem)
//图片cell
if i%2 == 1 {
//只展示图片
let pictureItem = ZJPictureTableItem(maxNumber: 5, column: 4, space: 15, width: self.view.frame.size.width, superVC: self, pictures: [image])
pictureItem.type = .read
section.add(item: pictureItem)
}else{
//添加图片
let pictureItem = ZJPictureTableItem(maxNumber: 5, column: 4, space: 15, width: self.view.frame.size.width, superVC: self)
pictureItem.type = .edit
section.add(item: pictureItem)
}
}
}
注:
TableView可以依赖storyboard、xib、纯代码来完成初始化,cell可以依赖于xib或纯代码构建。