美文网首页
数据驱动之 UITableViewController

数据驱动之 UITableViewController

作者: 啧啧同学 | 来源:发表于2020-08-05 07:15 被阅读0次

数据驱动:即使用数据驱动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页面的开发!~

相关文章

网友评论

      本文标题:数据驱动之 UITableViewController

      本文链接:https://www.haomeiwen.com/subject/sinbrktx.html