美文网首页swift开发框架
从零开始搭建swift开发框架(四)智能ViewControll

从零开始搭建swift开发框架(四)智能ViewControll

作者: 码农弟弟 | 来源:发表于2018-05-30 16:45 被阅读360次

    上一篇从零开始搭建swift开发框架(三)通用组件篇

    最近写了一个基于swift的ios开发框架swiftArch

    swift智能开发框架 用最少的代码完成页面 智能分页(策略设计模式) cell和section解耦 业务拆分 mock管理

    准备写一个系列的文章来介绍我是如何从零开始搭建

    本篇我将会介绍我的TableviewController

    PagingViewController

    这个viewcontroller的封装是这个框架的精华所在

    这个viewcontroller持有了之前介绍的组件 stateTableview
    所以它支持各种cover和header footer的定制
    最重要的是我用他做了两件事
    1.基于策略模式的 高复用 的智能分页
    2.cell和section的完全解耦

    例子是demo中的

    • PaingTalbeDemoViewController(pageNum pageSize分页,model解耦 section解耦 自动计算高度)
    • PagingOffsetIdDemoViewController(offsetId分页 model解耦 自动计算高度)
    • FeedsDemoViewController(mock数据 model解耦 手动计算高度)

    分页策略:我的分页规则是一个对象

    客户端通常分页规则的做法

    1.要么是纯手动计算 完全不封装(太麻烦)

    2.在基类中去计算(如果服务端分页有多个规则,那么就需要写多种基类)

    3.还有一些人直接采用超高度封装的方式 封装一个viewcontroller 然后直接把这个界面分页的url传进去

    而我的分页方法是一个策略 (策略模式:https://blog.csdn.net/aotian16/article/details/51382828)

    目前公司的项目 社交app 采取两种分页方式

    sql分页语句大致如下

    1.常规的pagesize pagenum 服务端sql语句例子

    <select id="getGame" resultMap="gameMap">
        select * from `t_arch_game` LIMIT #{beginNum},#{pageSize}
    </select>
    <select id="getGameTotal" resultType="java.lang.Integer">
        select count(*) from `t_arch_game`
    </select>
    

    2.采取最后一条数据的id offsetId的分页方式 服务端sql语句例子

    <select id="getFeed" resultMap="feedMap">
        select * from `t_arch_feed` where 1=1
        <if test="offsetId != null and direction=='old'">
            and id <![CDATA[ <]]>  #{offsetId}
        </if>
        order by id desc  
        LIMIT 0,#{pageSize}
    </select>
    

    我的做法是 定义一个策略

    ///分页策略
    
    protocol PagingStrategy{
        func addPage(info:Any)
        func resetPage()
        func getPageInfo()->Any
        func checkFinish(result:NSObject,listSize: Int) -> Bool
    } 
    
    

    我的PagingViewController持有一个stateTableview,并且将tableview的上下拉的事件绑定在viewcontroller的生命周期中

    列表下拉刷新的时候 会调用PagingViewController的onTableRresh 中调用

    self.pagingStrategy?.resetPage()
    

    在子类具体业务请求成功的时候需要用户调用

     /// - Parameters:
        ///   - resultData: 完整的返回值(因为我要从里面取分页信息比如total)
        ///   - dataSource: 完整的列表的数组(用于展示)
        ///   - pagingList: 分页相关的列表数组
        func loadSuccess(resultData:NSObject,dataSource:Array<NSObject>,pagingList:Array<NSObject>)
    

    我在这个方法中会调用

    self.pagingStrategy?.addPage(info: pagingList)
    let isFinish=self.pagingStrategy?.checkFinish(result: resultData, listSize: pagingList.count)
    

    好了 关于分页的策略 我源码的操作就只要根据以上的信息就可以自己实现策略了

    具体怎么实现 看默认提供的两种 NormalPagingStrategy和FeedPaingStrategy

    并且分别结合我两个demo去看PaingTalbeDemoViewController 和PagingOffsetIdDemoViewController

    使用策略的分页模式有什么好处呢

    1. 高度可扩展,如果再多出一个规则 我不需要去扩展基类,更不需要去修改vc中的业务代码
    2. 复用,初始化好这个对象之后,分页的任务就交给他了
    3. 如果你要用来接自己的项目,想改分页规则 ,你只需要去改我的策略(offsetId 和pagenum两种基本可以涵盖大部分的规则了,只是字段不一样或者具体的规则不一样,稍微改下就通用了 )

    分页到此为止 end

    cell 和section完全解耦

    先看下我demo中的三个列表页

    在他们对应的viewcontroller中几乎大同小异

    区别的部分仅仅是注册cell 分页策略对象初始化

    为啥没见到 numberOfRow , cellForIndex 这些来自 uitableviewDatasource和uitableviewDelegate这些标配方法呢? 老夫都帮你做好了 下面来分析

       override func onLoadData(pagingStrategy: PagingStrategy) { 
            let strategy:NormalPagingStrategy=pagingStrategy as! NormalPagingStrategy;
            let pageInfo:NormalPageInfo=strategy.getPageInfo() as! NormalPageInfo
            self.socailAppService.getGame(pageNum: pageInfo.pageNum, pageSize: pageInfo.pageSize, success: { [weak self] (gameListModel) in
                if let strongSelf = self {
                    if(pageInfo.isFirstPage()){
                        strongSelf.pagingList=(gameListModel?.listData)!
                        strongSelf.datasource=(gameListModel?.listData)!  
                        strongSelf.datasource.insert(GameDateModel(date:"今天"), at: 0)//section的model
                    }else{
                        strongSelf.pagingList+=(gameListModel?.listData)!
                        strongSelf.datasource.append(GameDateModel(date:"2011-11-\(Int(arc4random()%30)+1)"))
                        strongSelf.datasource = strongSelf.datasource + (gameListModel?.listData)!
                    }
                    //调用者必须维护两个列表
                    //1.和分页相关的列表
                    //2.总数据源的列表
                    strongSelf.loadSuccess(resultData: gameListModel!, dataSource: strongSelf.datasource, pagingList: strongSelf.pagingList)
                }
            }) {[weak self] (code, msg) in
                self?.loadFail()
            }
    

    datasource???

    pagingList???

    用户请求成功之后 需要维护两个列表

    1. 总的数据源就是你这个列表要展示的所有item dataSource

    2. 分页相关的数组,我用来到基类里面调用分页策略去计算分页

    举个例子吧 下面第三张的图片,一个列表,第一行是banner,下面文章数据(分页)

    或者是在一个动态列表 某几行插入广告,这些都需要去维护两个不同的数组

    这就完了???

    没有,你还需要注册一下cell

    override func registerCellModel() {
       super.registerCellModel()
       self.tableView?.registerCellNib(nib: R.nib.gameCell(), modelClass: GameModel.self)
    }
    override func registerSectionHeaderModel() {
        super.registerSectionHeaderModel()
        self.tableView?.registerHeaderClass(headerClass: GameDateHeader.self, modelClass: GameDateModel.self)
    }
        
    

    好了,差不多完了

    这里 我已经帮你把你的model和cell进行了绑定,并且会使用kvc的模式去设置cell中的model字段,你把cell做完就行了

    高度

    默认采用 UITableViewAutomaticDimension 也就是cell的autolayout来弄高度,经测试暂无太大性能问题,流畅没问题

    如果你不想用autolayout来做cell那也没事

    在子类viewcontroller中重写 这个方法,因为你的model和cell绑定,你能取到model就肯定知道是什么cell

    也可以具体参考这个例子 FeedsDemoViewController 手动计算高度

    func tableView(_ tableView: UITableView,heightForModel model: NSObject)->CGFloat {
            return 100
        }
    

    点击事件

    你可以把点击事件做到cell里面,然后发通知出来给vc接收

    还可以在viewcontroller重写以下方法

       override func registerEventforSectionHeader(header: UIView, model: NSObject) {
            if let item:GameDateModel = model as? GameDateModel {
                header.addTapGesture { [weak self] (tap) in
                    self?.view.makeToast("header被点击\(String(describing: item.date))")
                }
            } 
        }
        
        override func registerEventforCell(cell: UITableViewCell, model: NSObject) {
            if let item:GameModel = model as? GameModel { 
                cell.addTapGesture {[weak self] (tap) in
                    self?.view.makeToast("cell被点击\(String(describing: item.title))")
                }
            }
        } 
    

    使用说明就到此为止了,分享一下我是怎么实现sectionHeader

    1.首先至始至终用户只需要维护两个数组 一个用于分页 pagingList 一个用于展示datasource 在业务不复杂的情况下,他们就是同一个数组

    2.即便是section我也只需要你维护一个数组,你只要把section的模型注册好了,提供section功能就是为了做悬停

    如果你的datasource存在headerModel那必须第一个就是headerModel,不能以cellModel打头如果不存在那我没要求,

    你的datasource必须是 headerModel …..cellModel ... headerModel .. headerModel …..cellModel ...

    不能一开始就 cellModel …headerModel

    因为当存在headerModel的时候我是用headerModel的位置来做数组分割,把一个一位数组 根据headerModel拆分成一个二维数组

    那么问题来了,我第一个section不需要悬停,下面的某几行需要悬停咋办

    插入占位EmptyHeaderModel 这个model对应的header是一个高度为1背景全透明的headerView

    这样就不影响你的业务,第一行不需要悬停

    而且可以更加灵活的控制header的悬停,

    通过EmptyHeaderModel来控制上一个悬停的header的时机.

    具体还要自己看看我的demo

    本篇为完结篇

    相关文章

      网友评论

        本文标题:从零开始搭建swift开发框架(四)智能ViewControll

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