iOS 无感知上拉
本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
RxSwift编写wanandroid客户端现已开源
目前RxSwift编写wanandroid客户端已经开源了——项目链接。记得给个star喔!
附上一张效果图片:
本篇文章是从6月更文中热心网友的留言中进行的开发与探索:
6月确实因为日更的原因,这个功能没有实现,趁着7月的时候,解决了。
废话了这么多,那么我们进入主题吧。
什么是无感知上拉加载更多
什么是无感知,这个这样理解:在网络情况正常的情况下,用户对列表进行连续的上拉时,该列表可以无卡顿不停出现新的数据。
如果要体验话,Web端很多已经做到了,比如掘金的首页,还有比如掘金iOS的App,列表都是无感知上拉加载更多。
说来惭愧,写了这久的代码,还真的没有认真思考这个功能怎么实现。
如何实现无感知上拉加载更多
我在看见这位网友留言的时候,就开始思考了。
在我看来,有下面几个着手点:
列表滑动时候的是如何知道具体滑动的位置以触发接口请求,添加更多数据?
从UIScrollView的代理回调中去找和scrollView的位置(contentOffset)大小(contentSize)关系密切的回调。
网络上有没有比较成熟的思路?
顺着这条线,我先跑去看了UIScrollViewDelegate的源码:
public protocol UIScrollViewDelegate : NSObjectProtocol {
@available(iOS 2.0, *)
optional func scrollViewDidScroll(_ scrollView: UIScrollView) // any offset changes
@available(iOS 3.2, *)
optional func scrollViewDidZoom(_ scrollView: UIScrollView) // any zoom scale changes
.
.
.
.
.
.
/// 代码很多,这里就不放上来,给大家压力了。
}
直接上结论吧:看了一圈,反正没有和contentSize或者位置相关的回调代理。scrollViewDidScroll
这个回调里面虽然可以回参scrollView
,但是对于我们需要的信息还不够具体。
思考:既然UIScrollViewDelegate的代理没有现成的代理回调,自己使用KVO去监听试试?
网上的思路(一)
就在我思考的同时,我也在网络上需求实现这个功能的答案,让后看到这样一个思路:
实现方法很简单,需要用到tableView的一个代理方法,就可轻松实现。- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath就是这个方法,自定义显示cell。这个方法不太常用。但是这个方法可在每个cell将要第一次出现的时候触发。然后我们可设置当前页面第几个cell将要出现时,触发请求加载更多数据。
我看了之后,心想着,多写一个TableView的代理,总比写KVO的代码少,先试试再说,于是代码撸起:
extension SwiftCoinRankListController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let row = indexPath.row
let distance = dataSource.count - 25
print("row: \(row), distance:\(distance) ")
if row == distance {
loadMore()
}
}
}
本代码可以在开源项目中的SwiftCoinRankListController.swift
文件查看具体的逻辑,其主要就是通过cell显示的个数去提前请求加载数据,然后我们看看效果:
Gif可能看起来还好,我说我调试的感受:
虽然做到了上拉无感知,但是当手滑的速度比较快的时候,到底了新的数据没有回来,就会在底部等一段时间。
功能达到了,但是感受却不理想,果然还是监听的细腻程度不够。
网上的思路(二)
然后在继续的搜索中,我看到了另外一个方案:
很多时候我们上拉刷新需要提前加载新数据,这时候利用MJRefreshAutoFooter的属性triggerAutomaticallyRefreshPercent就可以实现,该属性triggerAutomaticallyRefreshPercent默认值为1,然后改成0的话划到底部就会自动刷新,改成-1的话,在快划到底部44px的时候就会自动刷新。
MJRefresh?使用MJRefreshAutoFooter,这个简单,我直接把基类的footer给替换掉就可以了,本代码可以在开源项目中的BaseTableViewController.swift
文件查看:
/// 设置尾部刷新控件,更新为无感知加载更多
let footer = MJRefreshAutoFooter()
footer.triggerAutomaticallyRefreshPercent = -1
tableView.mj_footer = footer
再来看看效果:
直接说感受:
代码改动性少,编写简单,达到预期效果,爽歪歪。比方案一更丝滑,体验好。
到此,功能就实现,难道就完了?
当然,不会,我们去看看源码吧。
MJRefresh代码的追根朔源
首先我们看看MJRefreshAutoFooter.h
文件:
这里有个专门的属性triggerAutomaticallyRefreshPercent
去做自动刷新,那么我们去MJRefreshAutoFooter.m
中去看看吧:
注意看喔,这个.m文件有一个- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
方法,并且还调用了super
,从这个方法名中我们可以明显的得到当scrollView的contentOffset变化的时候进行回调的监听。,我们顺藤摸瓜,看看super
是什么,会不会有新的发现:
稍微跟着一下源代码,MJRefreshAutoFooter的继承关系如下:
MJRefreshAutoFooter => MJRefreshFooter => MJRefreshComponent
所以这个super的调用我们就去MJRefreshComponent.m
里面去看看吧:
通过上面的截图我们可以得到下面的一些信息与结论:
MJRefreshComponent是通过KVO去监听scrollView的contentOffset变化,思路上我们对齐一致了。
该类并没有实现其具体方法,而是将其交由其子类去实现,这一点通过看
MJRefreshComponent.h
的注释可以得到:
- MJRefreshComponent从本质上更像虚基类。
总结
如果不是掘友提出这个问题,我可能都不会太仔细的去研究这个功能,也许继续普普通通的使用一般的上拉加载更多就够了。
这次的实践,其实是从思路到寻找方法,最后再到源码阅读。
思路也许不困难,但是真正一点点实现并完善功能,每一步都并不容易,这次我也仅仅是继续使用了MJRefresh这个轮子。
想起有一天,在群里吹水看见的一张图:
灵魂拷问,直击人心,大部分时间我们不也是云程序员呢?
知行合一方能开拓新的天地。