从零开始搭建swift开发框架(四)智能ViewControll
最近写了一个基于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
使用策略的分页模式有什么好处呢
- 高度可扩展,如果再多出一个规则 我不需要去扩展基类,更不需要去修改vc中的业务代码
- 复用,初始化好这个对象之后,分页的任务就交给他了
- 如果你要用来接自己的项目,想改分页规则 ,你只需要去改我的策略(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???
用户请求成功之后 需要维护两个列表
-
总的数据源就是你这个列表要展示的所有item dataSource
-
分页相关的数组,我用来到基类里面调用分页策略去计算分页
举个例子吧 下面第三张的图片,一个列表,第一行是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
本篇为完结篇