欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-22 08:12:59
...

上一篇从零开始搭建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

本篇为完结篇