内容简介:在大部分 App 中,在有 feeds 流之类列表的地方,由于后端数据一般采用分页加载,为了用户体验需要做预加载。最简单的加载方式,就是当列表显示的内容达到一定的数量时候,自动请求下一个分页。而这其实就是根据总行数,列表总高度,列表当前偏移值这三个数字决定是否要加载的关系式 fx。这里判断加载的策略,是需要自定义的,所以可以定义这样一个 Protocol。下面给出几种简单的加载策略。
在大部分 App 中,在有 feeds 流之类列表的地方,由于后端数据一般采用分页加载,为了用户体验需要做预加载。最简单的加载方式,就是当列表显示的内容达到一定的数量时候,自动请求下一个分页。
加载策略
而这其实就是根据总行数,列表总高度,列表当前偏移值这三个数字决定是否要加载的关系式 fx。这里判断加载的策略,是需要自定义的,所以可以定义这样一个 Protocol。
protocol ListPrefetcherStrategy { var totalRowsCount:Int { get set } func shouldFetch(_ totalHeight:CGFloat, _ offsetY:CGFloat) -> Bool } 复制代码
下面给出几种简单的加载策略。
阈值策略
设定一个阈值,比如 70%,显示内容达到阈值时进行加载。这种比较时候每一页的数量一致的情况。
同时要注意的是,这里的阈值应该是每个分页的阈值,总的阈值会随着列表长度增长。比如设置阈值为 70%,每页加载 10 个,第一页在加载到 7 个时进行预加载,第二页在第 17 个时进行预加载,此时阈值为 85%,而如果还是 70%,则会在第 14 个时进行预加载。所以这里的阈值需要动态增长。
假设我们已知目前列表的数据量和目前页数,根据每一页的阈值就可以动态计算总阈值:
// 数据总数除以当前页数,算出每一页的数量 let perPageCount = Double(totalRowsCount) / Double(currentPageIndex + 1) // 每页数量乘以页数加上每一页的阈值的和,就是总共需要的数量 let needRowsCount = perPageCount * (Double(currentPageIndex) + threshold) // 算出动态的阈值 let actalThreshold = needRowsCount / Double(totalRowsCount) 复制代码
这里需要记录当前的页数,笔者这里用了一个比较 trick 的做法,当行数增长时,则认为页数 +1,行数减少时,则认为页数归 0,适用于下拉刷新整个列表清空的情况。可以用属性观察 willSet 来改变页数。
struct ThresholdStrategy: ListPrefetcherStrategy{ func shouldFetch(_ totalHeight: CGFloat, _ offsetY: CGFloat) -> Bool { let viewRatio = Double(offsetY / totalHeight) let perPageCount = Double(totalRowsCount) / Double(currentPageIndex + 1) let needRowsCount = perPageCount * (Double(currentPageIndex) + threshold) let actalThreshold = needRowsCount / Double(totalRowsCount) if viewRatio >= actalThreshold { return true } else { return false } } var totalRowsCount: Int{ willSet{ if newValue > totalRowsCount { currentPageIndex += 1 } else if newValue < totalRowsCount { currentPageIndex = 0 } } } let threshold: Double var currentPageIndex = 0 public init(threshold:Double = 0.7) { self.threshold = threshold totalRowsCount = 0 } } 复制代码
剩余策略
也可以设定当列表剩余未展示行数即将少于某个值时,进行加载。这种适合每次分页数量不一定一致的情况。
struct RemainStrategy: ListPrefetcherStrategy{ func shouldFetch(_ totalHeight: CGFloat, _ offsetY: CGFloat) -> Bool { let rowHeight = totalHeight / CGFloat(totalRowsCount) let needOffsetY = rowHeight * CGFloat(totalRowsCount - remainRowsCount) if offsetY > needOffsetY { return true } else { return false } } var totalRowsCount: Int let remainRowsCount: Int init(remainRowsCount:Int = 1) { self.remainRowsCount = remainRowsCount totalRowsCount = 0 } } 复制代码
除法策略
还可以自己定义除数和余数,当达到余数时,进行加载。当然还要考虑一下实际余数比指定余数小的情况,这里笔者简单的往前面偏移一个除数的量进行判断。
struct OffsetStrategy: ListPrefetcherStrategy { func shouldFetch(_ totalHeight: CGFloat, _ offsetY: CGFloat) -> Bool { let rowHeight = totalHeight / CGFloat(totalRowsCount) let actalOffset = totalRowsCount % gap let needOffsetY = actalOffset > offset ? totalHeight - CGFloat(actalOffset - offset) * rowHeight : totalHeight - CGFloat(2 * gap + offset) * rowHeight if offsetY > needOffsetY { return true } else { return false } } var totalRowsCount: Int let gap: Int let offset: Int init(gap:Int, offset:Int) { self.gap = gap self.offset = offset totalRowsCount = 0 } } 复制代码
预加载组件
组件需要的信息有,scrollView,总行数,以及加载时候的通知外界。
定义一个 delegate 让外界遵循。
protocol ListPrefetcherDelegate:AnyObject { var totalRowsCount:Int { get } func startFetch() } 复制代码
然后用 KVO 监听 scrollView 的 contentSize,当发生变化是,就认为总行数发生改变,就可以将总行数设置给策略。监听 scrollView 的 contentOffset,变化时就是列表滚动,就可以用策略进行判断。
class ListPrefetcher:NSObject{ @objc let scrollView:UIScrollView var contentSizeObserver:NSKeyValueObservation? var contentOffsetObserver:NSKeyValueObservation? weak var delegate: ListPrefetcherDelegate? var strategy: ListPrefetcherStrategy public func start() { contentSizeObserver = observe(\.scrollView.contentSize) { (_, _) in guard let delegate = self.delegate else { return } self.strategy.totalRowsCount = delegate.totalRowsCount } contentOffsetObserver = observe(\.scrollView.contentOffset){ (_, _) in let offsetY = self.scrollView.contentOffset.y + self.scrollView.frame.height let totalHeight = self.scrollView.contentSize.height guard offsetY < totalHeight else { return } if self.strategy.shouldFetch(totalHeight, offsetY) { self.delegate?.startFetch() } } } public func stop() { contentSizeObserver?.invalidate() contentOffsetObserver?.invalidate() } public init(strategy:ListPrefetcherStrategy, scrollView:UIScrollView) { self.strategy = strategy self.scrollView = scrollView } } 复制代码
这样外界使用起来只需要提供策略和 scrollView,实现 delegate 的方法,然后在需要的时候 start 和 stop,就可以自动完成预加载的工作了。
以上所述就是小编给大家介绍的《iOS 实现简单的列表预加载》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 手把手实现图片懒加载+封装vue懒加载组件
- 如何实现 Bitmap 的高效加载?
- 【译】预加载视频实现快速播放
- [回炉计划]-实现一个图片预加载
- 原生js如何实现懒加载?
- 使用IntersectionObserver实现图片懒加载
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
人人都是产品经理
苏杰 / 电子工业出版社 / 2014-9-1 / CNY 55.00
《人人都是产品经理(纪念版)》为经典畅销书《人人都是产品经理》的内容升级版本。对于大量成长起来的优秀互联网产品经理,为数不少想投身产品工作的其他岗位从业者,以及更多有志从事这一职业的学生而言,这本书曾是他们记忆深刻的启蒙读物、思想基石和行动手册。作者以分享经历与体会为出发点,以“朋友间聊聊如何做产品”的语气,将自己数年产品工作过程中学到的思维方法与做事方式,及其它们对自己的帮助,系统性地梳理为用户......一起来看看 《人人都是产品经理》 这本书的介绍吧!