iOS 数据源控制TableView,尽可能减少逻辑判断

当一个界面的TableView/CollectionView的数据是写在本地的,比如"个人中心", "设置"等,如图:

模拟个人中心

通常情况:我们要在tableview的cellforrow和didselect方法里利用indexPath来做操作,有的同学甚至会在cell里对cell上的图片和文字进行判断来进行匹配

弊端:两个方法里利进行了大量的判断,而且在后期维护的时候,比如分组改变,则要修改数据源以及上面两个方法中的判断条件,代码读改都比较繁琐

于是有了新的思路,将数据源与Cell进行绑定,通俗点来说就是数据源model里包含cell上的一切属性,通过这些属性对cell上的view控件进行赋值和逻辑判断来展示cell,包括对应的点击事件, 代码结构为:

代码结构

观察"模拟个人中心",我们会发现cell上一共有4个控件,分别是: 图片, 标题, 更多, 版本号, 并且cell会有一个自身的点击事件. 于是model就应该有5个对应的属性:

class ZWTableViewExampleModel: NSObject {
    var imgStr: String? // cell图片
    var title: String? // cell标题
    var content: String? // cell内容
    var isMore: Bool? // 是否显示更多
    
    /// 点击每行cell点击事件,传索引
    var itemClick : ((_ indexPath: IndexPath) -> ())?
    
    /// 将点击事件分离
    class func initModel(title: String?, imgStr: String?, content: String?, isMore: Bool?) -> ZWTableViewExampleModel{
        let item = ZWTableViewExampleModel()
        item.imgStr = imgStr
        item.title = title
        item.content = content
        item.isMore = isMore
        return item
    }
    /// 将点击事件合并
    class func initModel(title: String?, imgStr: String?, content: String?, isMore: Bool?, itemClick: ((_ indexPath: IndexPath) -> ())?) -> ZWTableViewExampleModel{
        let item = ZWTableViewExampleModel()
        item.imgStr = imgStr
        item.title = title
        item.content = content
        item.isMore = isMore
        item.itemClick = itemClick
        return item
    }
}

Cell的布局和赋值代码, 利用model的属性分别进行赋值和展示逻辑判断(显示"更多"还是"版本号")如下:

class ZWTableViewExampleCell: UITableViewCell {
    var model: ZWTableViewExampleModel? {
        didSet {
            titleL.text = model?.title
            contentL.text = model?.content
            imgV.image = UIImage.init(named: model?.imgStr ?? "")
            moreImgV.isHidden = !(model?.isMore ?? true)
        }
    }
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    ///  创建代码写到Cell里,尽可能减少VC的体积
    class func cellWithTableView(_ tableView: UITableView) -> ZWTableViewExampleCell{
        var cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(self)) as? ZWTableViewExampleCell
        if cell == nil {
            cell = ZWTableViewExampleCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: NSStringFromClass(self))
        }
        return cell!
    }
    
    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(){
        addSubview(imgV)
        addSubview(titleL)
        addSubview(contentL)
        addSubview(moreImgV)
    }
    
    private lazy var imgV:UIImageView = {
        let imgV = UIImageView.init(frame: CGRect.init(x: 15, y: 15, width: 20, height: 20))
        return imgV
    }()
    
    private lazy var titleL:UILabel = {
        let label = UILabel.init(frame: CGRect.init(x: 45, y: 0, width: 200, height: 50))
        label.font = UIFont.systemFont(ofSize: 15)
        return label
    }()
    
    private lazy var contentL:UILabel = {
        let label = UILabel.init(frame: CGRect.init(x: UIScreen.main.bounds.width - 150 - 15, y: 0, width: 150, height: 50))
        label.textAlignment = .right
        label.textColor = .red
        label.font = UIFont.systemFont(ofSize: 15)
        return label
    }()
    
    private lazy var moreImgV:UIImageView = {
        let imgV = UIImageView.init(frame: CGRect.init(x: UIScreen.main.bounds.width - 20 - 15, y: 15, width: 20, height: 20))
        imgV.image = UIImage.init(named: "more")
        return imgV
    }()
}

控制器的代码, 如下:

class ZWTableViewExampleVC: UIViewController {
    var modelData = [[ZWTableViewExampleModel]]()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        title = "优雅的tableview"
        view.addSubview(tableView)
        setModelDataValue()
        
    }
    
    /// 初始化数据
    private func setModelDataValue() {
        /// 第0组
        /// 点击事件分离写法
        let model0_0 = ZWTableViewExampleModel.initModel(title: "分类", imgStr: "fenlei", content: nil, isMore: true)
        model0_0.itemClick = { indexPath in
            print("点击了分类")
        }
        /// 点击事件合并写法
        let model0_1 = ZWTableViewExampleModel.initModel(title: "我的课表", imgStr: "kebiao", content: nil, isMore: true) { indexPath in
            print("点击了我的课表")
        }
        
        /// 第1组
        let model1_0 = ZWTableViewExampleModel.initModel(title: "我的余额", imgStr: "qianbao", content: nil, isMore: true) { indexPath in
            print("点击了我的余额")
        }
        
        let model1_1 = ZWTableViewExampleModel.initModel(title: "消息", imgStr: "xiaoxi", content: nil, isMore: true) { indexPath in
            print("点击了消息")
        }
        
        /// 第2组
        let model2_0 = ZWTableViewExampleModel.initModel(title: "当前版本", imgStr: "lishi", content: "1.2.1", isMore: false) { indexPath in
            
        }
        
        /**
         无点击事件也可以这样写
        let model2_0 = ZWTableViewExampleModel.initModel(title: "当前版本", imgStr: "lishi", content: "lishi", isMore: false, itemClick: nil)
         
        let model2_0 = ZWTableViewExampleModel.initModel(title: "当前版本", imgStr: "lishi", content: "lishi", isMore: false)   
         */
        
        modelData = [
                    [model0_0, model0_1],
                    [model1_0, model1_1],
                    [model2_0]
                    ]
    }
    
    
    private lazy var tableView: UITableView = {
        let tableV = UITableView.init(frame: UIScreen.main.bounds, style: .grouped)
        tableV.backgroundColor = .white
        tableV.delegate = self
        tableV.dataSource = self
        tableV.rowHeight = 50
        tableV.tableFooterView = UITableViewHeaderFooterView.init()
        
        /// iOS 15 的 UITableView又新增了一个新属性:sectionHeaderTopPadding 会给每一个section header 增加一个默认高度,当我们 使用 UITableViewStylePlain 初始化 UITableView的时候,就会发现,系统给section header增高了22像素。
        /// 去除默认高度
        if #available(iOS 15.0, *) {
            tableV.sectionHeaderTopPadding = 0
        }
        return tableV
    }()

}

extension ZWTableViewExampleVC: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return modelData.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return modelData[section].count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = ZWTableViewExampleCell.cellWithTableView(tableView)
        cell.model = modelData[indexPath.section][indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
        let model = modelData[indexPath.section][indexPath.row]
        guard let click = model.itemClick else {
            return
        }
        click(indexPath)
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 15))
        view.backgroundColor = UIColor.init(red: 0.95, green: 0.95, blue: 0.95, alpha: 1)
    
        return view
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 15
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 15))
        return view
    }
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 0.01
    }
    
}

总结:
我们看到在cellForRowAt方法里只是执行了创建和赋值, 在didSelectRowAt里也只是执行了获取model的点击属性,并进行回调. 以上两个方法均没有出现通常的逻辑判断过程.

思路是: 将model闭包回调,从而通过回调在model创建的方法里进行执行, 从而使每一个model与点击事件进行绑定

优点:
从创建上来说,省去了上述两个代理方法里复杂逻辑判断,
从维护上来说,后期进行重新分组,只需要改变modelData数组里数据的顺序即可, 在更改其他属性时, 也不会涉及到以前的逻辑判断, 对应的点击事件更加清晰

ps: 除了以上写法,为了更轻便,还可以将model写成元组的形式:

/// 在VC类外定义元组
typealias ExampleItem = (title: String?, imgStr: String?, content: String?, isMore: Bool?, itemClick: ((_ indexPath: IndexPath) -> Void)?)

/// 在VC类里使用   
let item: ExampleItem = (title: "当前版本", imgStr: "lishi", content: "1.2.1", isMore: false, itemClick: { indexPath in
             
})
modelData = [[item]]

Demo: ZWModelBindAction

你可能感兴趣的:(iOS 数据源控制TableView,尽可能减少逻辑判断)