博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我

iOS 实现简单的列表预加载
阅读量:5789 次
发布时间:2019-06-18

本文共 4864 字,大约阅读时间需要 16 分钟。

在大部分 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,就可以自动完成预加载的工作了。

最后

完整的 ,也可以自定义不同的策略实现。

转载地址:http://ghmyx.baihongyu.com/

你可能感兴趣的文章
CSS3 高级技术
查看>>
原型模式(Prototype )
查看>>
201521123009 《Java程序设计》第1周学习总结
查看>>
年终述职--常见问题分析解答
查看>>
C#_细说Cookie_Json Helper_Cookies封装
查看>>
对事件循环的一点理解
查看>>
在mui中创建aJax来请求数据..并展示在页面上
查看>>
spring 之AOP
查看>>
总结 15/4/23
查看>>
守护进程
查看>>
Windows 7环境下网站性能测试小工具 Apache Bench 和 Webbench使用和下载
查看>>
C#常见错误解决方法
查看>>
安装cnpm (npm淘宝镜像)
查看>>
js 利用事件委托解决mousedown中的click
查看>>
游戏设计艺术 第2版 (Jesse Schell 著)
查看>>
Java 面向对象(基础) 知识点总结I
查看>>
miniUI mini-monthpicker ie8兼容性问题
查看>>
读书笔记《自控力》
查看>>
基于神念TGAM的脑波小车(1)
查看>>
ceph集群搭建Jewel版本
查看>>