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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容