简化TableViewController

不知道你有没有写过这样界面,简单的信息流展示界面,没有复杂的结构,只有一个section,数据来源于同一个接口。写这样的界面有一些固定重复的代码需要写 —— 网络请求、网络相关的界面处理、tableview的代理。针对只有一个section、数据来源单一的界面,可以提取出一个框架,来完成这套固定的流程,减少重复代码

还有一些更基础的代码,比如刷新、加载更多功能、空白页的显示,但刷新、加载更多、空白页的显示属于更大范围的重复代码,不止这篇博文讨论的【单section,数据来源于同一个接口】的界面,更多其他类型的界面也会用到,所以这些更基础的功能就不提及了。

之所以要求单section,是因为多section和单section所需要的数据结构差异比较大,单section的界面通常用一个数组做存储就行了,但多section的就不能只用一个数组做存储;数据来源于同一个接口也是一样的道理,数据处理的简单,上拉刷新的时候是replace操作,下拉刷新的时候是append操作

【单section单一数据来源】界面 加载数据流程:

代码实现大概就是下面这样:

struct FlowModel {
}

class InformationFlowController: UIViewController {
    var datas: [FlowModel] = []
    var tableView: UITableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //加载界面
        setupUI()
        
        //加载数据
        loadData()
    }
    
    func loadData() {
        //用自己封装的第三方进行网络请求
        Network.request(api, success: { (data: Data) in
            
            //拿到数据后转换成目标模型
            let models: [FlowModel] = transDataToModels(data)
            
            //存储数据
            self.datas = models
            
            //重载数据
            tableView.reloadData()
        }) { (error) in
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datas.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return HeightOfCellAtIndexPath
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = datas[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath)
        cell.setData(data)
        return cell
    }
}
复制代码

这样的页面加载数据流程类似,但是流程中会需要情景数据,其中:

  • 网络请求:需要接口地址
  • 解析数据:需要知道model类型用来解析数据
  • tableView.numberOfRow:model数组的count
  • tableView.heightForRow:该index下的model对应cell的高度
  • tableView.cellForRow:需要model对应的cell类型用来取cell,还要根据需要给cell注入数据

把这些情景数据剥离出流程,方案如下:

  • 网络请求:需要接口地址 —— 通过func来获取,具体的子controller通过覆写func来提供接口地址
  • 解析数据:需要知道model类型用来解析数据 —— 使用泛型
  • tableView.numberOfRow:model数组的count —— 内置一个数据存储结构
  • tableView.heightForRow:该index下的model对应cell的高度 —— 数据模型提供高度
  • tableView.cellForRow:需要model对应的cell类型用来取cell,还要根据需要给cell注入数据 —— 数据模型需要提供cell类型,cell需要一个通用的注入数据口

使用泛型解决数据模型不同的问题,是基于现在大部分解析json数据的第三方都是根据类型来进行解析的

把height放到数据中有一个好处,就是当cell不定高时,可以根据数据计算出高度

把cell class放到数据中,可以应对信息流中多种cell类型的情况,根据数据选择cell类型。

根据以上解决方案,整理出一个基类:

class BaseController<Model: IUIInfo>: UIViewController {
    var datas: [Model] = []
    var tableView: UITableView = UITableView()
    
    func getAPI() -> API {
        //override this to provide api
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //加载界面
        setupUI()
        
        //加载数据
        loadData()
    }
    
    func loadData() {
        //用自己封装的第三方进行网络请求
        Network.request(getAPI(), success: { (data: Data) in
            
            //拿到数据后转换成目标模型
            let models: [Model] = transDataToModels(data)
            
            //存储数据
            self.datas = models
            
            //重载数据
            tableView.reloadData()
        }) { (error) in
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datas.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return datas[indexPath.row].height
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = datas[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: data.cellClass().identifier, for: indexPath)
        (cell as? ICanAssignData)?.setData(delegate: self, indexPath: indexPath, data: data)
        return cell
    }
}

protocol IUIInfo {
    func cellClass() -> UITableViewCell.Type
    var height: CGFloat {get}
}

protocol ICanAssignData {
    func setData(delegate: Any?, indexPath: IndexPath, data: Any) // 传这几个参数是经验之谈
}

struct FlowModel: IUIInfo {
    func cellClass() -> UITableViewCell.Type {
        return FlowModelCell.self
    }
    
    var height: CGFloat {
        return 100
    }
}

class FlowModelCell: UITableViewCell, ICanAssignData {
}

extension UITableViewCell {
    
    static var identifier: String {
        return String(describing: self)
    }
    
}
复制代码

这样InformationFlowController可以简化成这样

class InformationFlowController: BaseController<FlowModel> {
    override func getAPI() -> API {
        return informationFlowAPI
    }
}
复制代码

更新流程图如下:

可以根据需要预留数据处理前后的方法,留给具体情景下的子controller处理数据的机会。

数据模型遵循IUIInfo协议,cell遵循ICanAssignData协议,就可以省下 网络请求、网络请求后通用的数据处理、网络请求相关的界面状态、tableview基础协议实现,应该算是一笔划算的买卖

转载于:https://juejin.im/post/5cd1783c6fb9a031fb2ce8b4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值