美文网首页
Swift利用Protocol封装UITableView下拉刷新

Swift利用Protocol封装UITableView下拉刷新

作者: 九龙 | 来源:发表于2020-03-12 16:55 被阅读0次

前言:在公司iOS端项目中一直有一个比较棘手的问题就是列表的问题,列表控制器包括列表的展示、下拉刷新、上拉加载更多数据、以及错误界面的处理。逻辑复杂,以前的代码冗余度也比较高,所以抽出时间对列表做了基于协议的封装RefreshTableViewProtocol

RefreshTableViewProtocol前必须要介绍ListResponseProtocol协议

protocol ListResponseProtocol:HandyJSON{
    associatedtype T
    var slide: Int {get set}
    var top: String {get set}
    var bottom: String {get set}
    var hasMore: Bool {get set}
    var list: [T] {get set}
    mutating func add<List:ListResponseProtocol>(data:List) where List.T == T
}
extension ListResponseProtocol{
    mutating func add<List:ListResponseProtocol>(data:List) where List.T == T {
        slide = data.slide
        top = data.top
        bottom = data.bottom
        hasMore = data.hasMore
        list.append(contentsOf: data.list)
    }
}

ListResponseProtocol是定义的我们服务器返回的列表数据的整体结构,定义这个主要是加载更多的列表需要固定的数据结构, 服务器返回的数据结构是稳定的,当然这也有一定的灵活性,只要遵循了这个协议的数据都可以被列表协议使用
比如QueryListResponse由于项目的原因需要特殊的字段都可以,提高灵活性

class QueryListResponse<T>: HandyJSON,ListResponseProtocol {
    var slide = 0
    var top = ""
    var bottom = ""
    var hasMore = false
    var list: [T] = []
  //这个是新加的字段来接受服务器的数据
    var user:QueryUser?
    required init() {}
}
接下来看看RefreshTableViewProtocol的代码

(这里的代码还是可以优化的,只是最近有点忙,一直没做)

protocol RefreshTableViewProtocol:TableViewProtocol,EmptyProtocol,LoadingAnimationProtocol{
    associatedtype T:ListResponseProtocol
    var http:ModelHTTP<T> { get set }
    var resp: T {get set}

    func loadDataSuccess(_ model: T,_ isLoadAnimation:Bool,_ append:Bool)
    func processData(_ model: T, append: Bool)
    func loadDataFailed(_ code: Int?, _ msg:String,_ isLoadAnimation:Bool,_ append:Bool)

    //在请求数据的前要做的其它事情
    func beforeLoadDataAction(_ isLoadMore:Bool,_ isLoadAnimation:Bool)
    //在处理数据的时候要做的其它事情
    func processDataOtherAction(_ model: T, _ append: Bool)
    //处理请求错误
    func loadingFailure(_ code: Int?, _ msg:String,_ isLoadAnimation:Bool)
}

RefreshTableViewProtocol遵守了TableViewProtocolEmptyProtocolLoadingAnimationProtocol这三个协议,协议里的默认实现就不列出来了都是根据自己项目的需求来展示UI
TableViewProtocol主要提供Tableview创建的一些便利方法

protocol TableViewProtocol:class {
    var dataTableView:UITableView {get set}
    var header:MJRefreshHeader { get set }
    var footer:MJRefreshFooter { get set }
    var footerView:NoMoreFooter { get set }
    func headerRefreshAction()
    func footerRefreshAction()
}

EmptyProtocol主要提供的是空白页面的处理

protocol EmptyProtocol:EmptyViewDelegate {

    func showEmptyWith(type: EmptyType, on superView: UIView, withText text: String? , isFullScreen: Bool, offSetY: CGFloat?, showRefreshBtn: Bool)

    func hideEmpty(on superView: UIView)
}

LoadingAnimationProtocol主要是提供加载的动画

protocol LoadingAnimationProtocol:UIViewController {

    func showLoading(offsetX: CGFloat,offsetY: CGFloat, withShadowBackground: Bool)

    func hideLoading(withShadowBackground: Bool)

    func hideLoadingWithTime(time: TimeInterval)
}

RefreshTableViewProtocol遵守了上面的三个协议就有了三个协议提供的能力
associatedtype T:ListResponseProtocol这个定义的协议的泛型,就是在遵守这个协议的时候必须指定的一个数据类型,并且这个数据类型必须遵守ListResponseProtocol这个协议
var http:ModelHTTP<T> { get set }这个是协议必须提供一个ModelHTTP<T>的数据,这个其实是发起网络请求,这个网络请求是前期封装的一个网络请求工具,在这里刚好派上用场
var resp: T {get set}主要是对网络请求的数据做一个缓存
其它的方法都是为了使用的灵活性而暴露的,可以根据使用时自行定义

接下来看下协议的默认实现

extension RefreshTableViewProtocol{
//下拉刷新
    func headerRefreshAction() {
        loadDatas()
    }
//上拉加载更多
    func footerRefreshAction() {
        loadMoreDatas()
    }
//加载数据有加载动画
    func loadDataWithAnimation() {
        baseLoadData(isLoadMore: false, isLoadAnimation: true)
    }
//加载数据无加载动画
    func loadDatas(){
        baseLoadData(isLoadMore: false, isLoadAnimation: false)
    }
//加载更多数据
    func loadMoreDatas() {
        baseLoadData(isLoadMore: true, isLoadAnimation: false)
    }
//错误和空白界面的刷新按钮点击
    func emptyViewDidTapRefresh(_ emptyView: EmptyView){
        loadDataWithAnimation()
    }
//处理数据
    func processDataOtherAction(_ model: T, _ append: Bool){
        if model.list.isEmpty {
            showEmptyWith(type: .noData, on: dataTableView)
        }else{
            hideEmpty(on: dataTableView)
        }
    }
    func beforeLoadDataAction(_ isLoadMore:Bool,_ isLoadAnimation:Bool){}
}

extension RefreshTableViewProtocol{
//加载数据
    func baseLoadData(isLoadMore:Bool = false,isLoadAnimation:Bool = false){
        beforeLoadDataAction(isLoadMore,isLoadAnimation)
        if isLoadAnimation{
            showLoading()
        }
        if isLoadMore{
            http.parameters?.appendListParameters(resp: resp)
        }else{
            http.parameters?["top"] = ""
            http.parameters?["bottom"] = ""
            http.parameters?["slide"] = ""
        }
        hideEmpty(on: dataTableView)
        
        http.successOnlyRespCallback = { [weak self] model in
            guard let strongSelf = self else { return }
            strongSelf.loadDataSuccess(model, isLoadAnimation, isLoadMore)
        }

        http.failedCallback = { [weak self] code,msg in
            guard let strongSelf = self else { return }
            strongSelf.loadDataFailed(code, msg, isLoadAnimation, isLoadMore)

        }
        http.doHTTP()
    }
//成功
    func loadDataSuccess(_ model: T,_ isLoadAnimation:Bool,_ append:Bool){
        hideLoading()
        endRefresh()
        processData(model, append: append)
    }
//失败
    func loadDataFailed(_ code: Int?, _ msg:String,_ isLoadAnimation:Bool,_ append:Bool){
        hideLoading()
        endRefresh()
        loadingFailure(code,msg,isLoadAnimation)
    }
    
    func endRefresh(){
        if let header = dataTableView.mj_header{
            header.endRefreshing()
        }
        if let footer = dataTableView.mj_footer{
            footer.endRefreshing()
        }
    }
    
    //处理数据
    func processData(_ model: T, append: Bool){
        if append {
            resp.add(data:model)
        }else{
            resp = model
        }
        relfreshMJFooter(tableView:dataTableView, resp: model)
        processDataOtherAction(model,append)
        dataTableView.reloadData()
    }
    
    //处理请求错误
    func loadingFailure(_ code: Int?, _ msg:String,_ isLoadAnimation:Bool){
        if isLoadAnimation{
            dataTableView.mj_header = nil
            guard let code = code else { return }
            if code == netFailDefaultCode {
                showEmptyWith(type: .netFail, on: dataTableView)
            } else {
                showEmptyWith(type: .error, on: dataTableView)
            }
            resp = T()
        }else{
            toast(msg)
        }
    }
//处理footer
    func relfreshMJFooter<T:ListResponseProtocol>(tableView: UITableView, resp: T){
        if resp.hasMore {
            tableView.mj_footer = footer
            tableView.tableFooterView = nil
            return
        }
        
        if !resp.list.isEmpty {
            tableView.mj_footer = nil
            tableView.tableFooterView = footerView
            return
        }
        
        tableView.mj_footer = nil
        tableView.tableFooterView = nil
    }
}

这里面封装了默认的使用逻辑,处理了网络请求和数据的处理,对下拉刷新,和上拉加载更多,对空白界面都封装了默认的实现。

实际的使用体验

class BalanceListViewController: UIViewController,RefreshTableViewProtocol {

    var type:BalanceItemType = .All
    
//网络请求
    lazy var http: ModelHTTP<BalanceListResponse<Balance>> = {

        var parameters:Dictionary<String,Any> = ["type":type.rawValue]

        return ModelHTTP<BalanceListResponse<Balance>>(method: .get,parameters:parameters,api:.payRecords)
    }()
//请求的列表数据
    var resp: BalanceListResponse<Balance> = BalanceListResponse<Balance>()
//列表数据类型
    typealias T = BalanceListResponse<Balance>

    var isLoading : Bool = false

    
    lazy var dataTableView: UITableView = {
        let _dataTableView = UITableView(frame: .zero, style: .plain)
        _dataTableView.separatorStyle = .none
        _dataTableView.backgroundColor = Color_F2F6F8
        _dataTableView.showsVerticalScrollIndicator = false
        _dataTableView.mj_header = header
        _dataTableView.dataSource = self
        _dataTableView.delegate = self
        _dataTableView.rowHeight = 70.0
        _dataTableView.ut_registerNibCell(BalanceListCell.self)
        return _dataTableView
    }()

//对header进行自定义设置
    lazy var header: MJRefreshHeader = {
        let header = creatMJHeader() as? RefreshHeader
        header?.backgroundColor = Color_F2F6F8
        header?.grayLayer.backgroundColor = Color_F2F6F8.cgColor
        return header ?? creatMJHeader()
    }()
//对footer进行自定义设置
    lazy var footer: MJRefreshFooter = {
        let footer = creatMJFooter()
        footer.backgroundColor = Color_F2F6F8
        return footer
    }()
//对footerView进行自定义设置
    lazy var footerView: NoMoreFooter = {
        let footerView = creatFooterView()
        footerView.backgroundColor = Color_F2F6F8
        return footerView
    }()

控制器只需要遵守协议,并实现就可以了,列表的显示由控制器自己处理。就这样就把下来刷新和加载更多,以及网络请求数据错误的展示的逻辑都隐藏起来了,把冗余的代码去掉了。

写在最后,基于老项目的原因牵一发而动全身,这个协议还不是很彻底也不是很纯净,里面的一些东西对于别的项目可能是不需要的,这个提供一个思路大家可以根据自己的实际项目逻辑优化

喜欢就点个赞👍

相关文章

网友评论

      本文标题:Swift利用Protocol封装UITableView下拉刷新

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