By
GeekerHua
总阅读次
在app的开发中, 出现最多的一个情况就是显示一个列表来展示数据, 就像刷微博一样, 要能够上拉加载更多, 下拉进行刷新。 但在实际开发过程中, 需要考虑的情况会更多。 我们使用 header
来表示下拉刷新控件, 使用 footer
来表示上拉加载控件。
在不考虑缓存的情况下单数据源tableView需要考虑以下注意点
- 首次进入这个页面, 没有数据需要进行首次数据加载
- 首次加载过程中不能显示footer
- 下拉刷新需要用返回的结果覆盖数据源的数据
- 下拉刷新后需要还原footer的状态(变更为可以加载更多)
- 上拉加载成功后需要根据返回数据数量来判断是否还有更多数据, 没有更多数据需要修改footer的状态为
没有更多数据了
, 并禁止上拉刷新功能。
- 首次加载数据如果没有网络连接或者加载失败, 需要显示一个失败页面, 点击失败页面能够重新进行网络请求获取数据。
- 每次进行下拉刷新都需要将当前page设为
1
- 每次进行上拉加载前都需要将当前page进行
+1
操作
- 每次上拉加载失败都需要将当前page进行
-1
操作, 以还原防止, 下次上拉加载page多加了的问题
如果是多数据源的tableView则需要考虑的更多
- 首先就是不同数据源的page记录, 每个数据源都需要对应一个自己的page
- 每个数据源都需要记录是否还有更多数据可供加载
- 甚至每个数据源拥有各自的没数据的文字提示
- 实际项目中, 为了一些效果, 还需要记录当前数据源是否处于加载数据状态, 以此来显示某些加载页面。
- 可能还会有一个个性化的定制需求
综上所述, 仅仅是一个控制器的数据加载逻辑就有这么多, 如果有很多这样类似的页面, 每次都要考虑这么多问题, 难免会有不少疏忽, 而且要实现这些功能会产生大量的重复代码, 这肯定是我们不希望看到的。
分析上述需求我们发现, 实际上通常我们所接触的 tableView
大体上也就需要注意这么多问题, 而且为了整个工程的统一性, 一般情况所有的处理也是采用同一套逻辑, 因此我们完全可以把这些逻辑统一起来, 使用一个 Protocol
来实现这些逻辑。 得益于Swift强大的 Protocol Extention
大部分情况我们只需要在合适的关键点调用几个方法就可以了, 所有的逻辑默认都已经实现了。 Controller
中的代码更少了, 不相关的逻辑都封装好了, 逻辑更加简洁了。
控制器使用代码
class PYMyOrderListController: PYBaseViewController, Refreshable { internal var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] = [(1, false, false, "没有更多订单了“)] // 定义每个数据源需要的四个属性, 分别是当前页码, 是否被正在加载中, 是否没有更多数据可供加载了。 没有数据可供加载的footer文字 internal var currentIndex = 0 // 当前现实的数据源索引 internal var refreshTable: UITableView = UITableView() // 当前tableView
override func viewDidLoad() { view.addSubview(tableView) tableView.snp_makeConstraints { (make) in make.left.right.bottom.equalTo(0) make.top.equalTo(segementView.snp_bottom) } refreshTable = tableView // 赋值当前tableView setupRefreshHeader() //初始化下拉刷新控件 setupRefreshFooter() // 初始化上拉加载控件 } /// 加载数据的方法 /// /// - parameter isRefresh: 是否是下拉刷新 func refreshData(isRefresh: Bool) { refreshStatus[currentIndex].isLoading = true // 修改当前数据源的加载状态为正在加载 let indexItem = currentIndex let url = URL_OrderList + "/\(type)/\(PageCount)" + "/\(refreshStatus[currentIndex].page).json" let request = PYNetWorkTools. GET(url, hudType: . None, failer: { [weak self] (failerTuples) in guard self != nil else{ return } self?.loadFailer(failerTuples) // 加载失败的方法 }) {[weak self] (response, jsonResult) in guard self != nil else{ return } self?.tableView.hiddenNoNetPlace() let array = PYOrderListModel.modelArray(jsonResult) if isRefresh { self?.totalArray[indexItem] = array } else { self?.totalArray[indexItem] += array } self?.loadSuccess(array.count < PageCount) // 加载成功的方法, 并传递一个是否还有更多数据的返回值 } if request != nil { requests.append(request!) } } }
|
以上这些代码就可以实现上述所有的功能, 怎么样, 是不是很有魅力呢? 实例中使用了 Refreshable
协议, 这套协议可以用在 UIViewController
和 UITableViewController
中, 其中的 refreshTable
就是为了适配 UIViewController
所增加的一个属性, 否则连这个属性都不用写了。
代码中能看到的协议中定义的内容如下
属性
- refreshStatus
- currentInidex
- refreshTable
方法
- refreshData(isRefresh: Bool)
- setupRefreshHeader()
- setupRefreshFooter()
- loadFailer(failerTuples: FailerTuples)
- loadSuccess(noMoreData: Bool?)
让我们先看看 Refreshable
这个协议里是怎么写的
protocol Refreshable { func refreshData(isRefresh: Bool)
var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] {set get}
var currentIndex: Int {set get}
var refreshTable: UITableView {get set} }
extension Refreshable where Self: UIViewController {
mutating func loadFailer(failerTuples: (type: NetFailerType, desc: String?)?) { refreshStatus[currentIndex].page -= 1 if refreshStatus[currentIndex].page < 0 { refreshStatus[currentIndex].page = 0 } refreshStatus[currentIndex].isLoading = false refreshFooter() if refreshTable.mj_header != nil { refreshTable.mj_header.endRefreshing() }
if failerTuples?.type == NetFailerType. NoNet { if refreshTable.visibleCells.isEmpty { refreshTable.showNoNetPlace({ [weak self] in guard self != nil else { return } self?.refreshData(true) }) } else { showToast(failerTuples?.type.rawValue ?? "") } } refreshTable.reloadData() }
mutating func loadSuccess(noMoreData: Bool?) { refreshStatus[currentIndex].isLoading = false if let noMoreData = noMoreData where refreshTable.mj_footer != nil { refreshStatus[currentIndex].noMoreData = noMoreData refreshTable.mj_footer.hidden = false refreshFooter() } if refreshTable.mj_header != nil { refreshTable.mj_header.endRefreshing() } refreshTable.reloadData() }
func setupRefreshHeader() { let header = MJRefreshNormalHeader {[weak self] () -> Void in guard self != nil else { return } self?.refreshTable.mj_footer.resetNoMoreData() self?.refreshStatus[self!.currentIndex].page = 1 self?.refreshData(true) self?.refreshStatus[self!.currentIndex].isLoading = true } refreshTable.mj_header = header }
func setupRefreshFooter() { let footer = MJRefreshBackStateFooter {[weak self] () -> Void in guard self != nil else { return } self?.refreshStatus[self!.currentIndex].page += 1 self?.refreshData(false) self?.refreshStatus[self!.currentIndex].isLoading = true } footer.hidden = true refreshTable.mj_footer = footer }
func refreshFooter() { if refreshTable.mj_footer != nil { if refreshStatus[currentIndex].noMoreData { refreshTable.mj_footer.endRefreshingWithNoMoreData() } else { refreshTable.mj_footer.endRefreshing() } } } }
|
整个协议简洁明了, 没有一句废话。 就把众多需要的功能及注意点都涵盖了。 该协议具有以下特点。
- 支持
UITableViewController
以及 UIViewcontroller
的刷新处理。
- 支持多数据源的切换加载。
这两个特性已经涵盖了日常开发中常见的所有情况。 当然你也可以只添加上拉加载, 不添加下拉刷新功能, 总之, 这些都随便。
源码在这
本文作者: GeekerHua
本文链接: https://blog.geekerhua.com/swift_refreshable/
文章首发: 同步首发于 语雀 及 GeekerHua的blog
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!