iOS 实现简单的列表预加载

栏目: IOS · 发布时间: 5年前

内容简介:在大部分 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 实现简单的列表预加载》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Nature of Code

The Nature of Code

Daniel Shiffman / The Nature of Code / 2012-12-13 / GBP 19.95

How can we capture the unpredictable evolutionary and emergent properties of nature in software? How can understanding the mathematical principles behind our physical world help us to create digital w......一起来看看 《The Nature of Code》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具