美文网首页iOS 开发
简化TableViewController

简化TableViewController

作者: 亲爱的八路 | 来源:发表于2019-05-07 20:12 被阅读17次

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

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

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

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


    单section单数据源流程.png

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

    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
        }
    }
    

    更新流程图如下:


    单section单数据源优化后的流程.png

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

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

    相关文章

      网友评论

        本文标题:简化TableViewController

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