swift开发框架

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

2018-05-30  本文已影响360人  码农弟弟

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

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

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

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

本篇我将会介绍我的TableviewController

PagingViewController

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

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

例子是demo中的

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

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

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

本篇为完结篇

上一篇下一篇

猜你喜欢

热点阅读