数据驱动:即使用数据驱动ui的展示,而不需要手动去调整ui,从而将数据与ui进行绑定,界面的布局全部由数据来控制;数据变化,界面ui跟着变化,数据不变,则ui不会由任何的操作或者其他的相关布局导致变化!
用数据驱动的方式,创建UITableViewController基类,方便的创建各种类型的列表页面
首先通过一个简单的列表页的开发来看一下最终的使用的方式
//首先创建一个VC,继承自基类 - FCBaseListViewController
class FCTestListController: FCBaseListViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "测试列表页"
// 刷新页面
refreshUI()
}
//注册cell (cell class, cellId )
override func registerCells() -> [(Any, String)] {
return [(TestListCell.self, TestListCell.description())]
}
//tableView的二维数组,对应各section
override func makeDisplayItems() -> [[YFCellItem]] {
let model = FCTestModel()
let item = (TestListCell.description(), 99.0, model, { [weak self] (row) in
guard let strongSelf = self else { return }
jumpToPageB()
})
return [[item]]
}
// 点击cell的操作
func jumpToPageB() {
//do jumper
}
至此,一个列表页面就这样简单的实现了!~可以看到,仅需要 注册 对应的cell: registerCells() 及 构建二维数据makeDisplayItems(), 最后通过refreshUI(),便可方便的构建各种类型的列表页面,不需要重复搬运/copy tableView的delegate、datasource及对应tableView中cell的创建、刷新及点击操作等!
接下来看一下 “列表基类”FCBaseListViewController 的设计
1.首先为了可读性,定义data Config 的别名
//(cellId, cell height, cell config data, cell click action)
typealias YFCellItem = (String, CGFloat?, Any?, ((IndexPath) -> Void)?)
- cellId : 需要注册的cell 的标示
- cell height: cell的高度,如果设置为nil,则为自适应高度,即根据cell里面的各元素的布局来确定高度
- cell config data: 用来渲染cell的 viewModel
- cell click action: 点击cell的事件(也可以通过在)
面向协议的思想,定义需要能够被该列表基类注册的cell,必须要实现 YFListCellProtocol 协议,定义为:
protocol YFListCellProtocol {
//data为任意用于渲染cell的 viewModel
func refresh(_ data: Any?)
}
创建tableView实例变量,并布局
func setupSubviews() {
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
make.size.equalToSuperview()
}
}
//MARK: property
//数据的二维数据,对应各sectionList
var listData: [[YFCellItem]] = []
// 对外提供tableStyle的变更操作
var tableStyle: UITableView.Style {
return .grouped
}
lazy var tableView: UITableView = {
let table = UITableView(frame: CGRect(), style: self.tableStyle)
table.delegate = self
table.dataSource = self
table.estimatedRowHeight = 50
table.backgroundColor = .white
table.separatorStyle = .none
return table
}()
基类实现刷新refresh及注册数据的调用
private func registerTableViewCells() {
for item in registerCells() {
if let cellClass = item.0 as? UITableViewCell.Type {
tableView.register(cellClass, forCellReuseIdentifier: item.1)
}
if let cellNibName = item.0 as? String {
tableView.register(UINib.init(nibName: cellNibName, bundle: nil), forCellReuseIdentifier: item.1)
}
}
}
func reloadVisiableCells() {
if let visiableIndexPaths = tableView.indexPathsForVisibleRows {
tableView.reloadRows(at: visiableIndexPaths, with: .none)
}
}
func refreshUI() {
listData = makeDisplayItems()
tableView.reloadData()
}
给外部提供来两个快速获取数据的便捷方法:
//MARK: helper
// 获取IndexPath对应cell 的data config
func rowItemAt(_ indexPath: IndexPath) -> YFCellItem {
let sectionArray = sectionArrayAt(indexPath.section)
guard indexPath.row < sectionArray.count else {
return YFCellItem("", nil, nil, nil)
}
return sectionArray[indexPath.row]
}
//获取对应section 的data config list
func sectionArrayAt(_ section: Int) -> [YFCellItem] {
guard section < listData.count else {
return [YFCellItem]()
}
return listData[section]
}
子类实现的方式(即子类配置对应的cell)
//MARK: for sub class
func makeDisplayItems() -> [[YFCellItem]] {
return []
}
//需要注册的cell,子类覆盖,any为UITableViewCell.type或string(nib名称)
func registerCells() -> [(Any, String)] {
return []
}
基类实现tableView 对应的UITableViewDelegate, UITableViewDataSource:, 使用extension方式,使可读性更高
func numberOfSections(in tableView: UITableView) -> Int {
return listData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionArray = sectionArrayAt(section)
return sectionArray.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//(cellId, cell height, cell config data, cell click action)
let item = rowItemAt(indexPath)
return item.1 != nil ? item.1! : UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.1
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 15.0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .white
return view
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .white
return view
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = rowItemAt(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: item.0)
if let mineCell = cell as? YFListCellProtocol {
mineCell.refresh(item.2)
}
if let lineCell = cell as? YFBaseListCell {
configDividerLines(lineCell, indexPath)
}
return cell ?? UITableViewCell()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = rowItemAt(indexPath)
item.3?(indexPath)
}
以上已完成基类的所有设置,collectionView也可以用类似的办法,实现数据驱动,使继承与基类的controller能快速的实现collection页面的开发!~
网友评论