iOS 开发

简化TableViewController

2019-05-07  本文已影响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
    }
}

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

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

使用泛型解决数据模型不同的问题,是基于现在大部分解析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基础协议实现,应该算是一笔划算的买卖

上一篇 下一篇

猜你喜欢

热点阅读