UIScrollView+无限滚动

作为 UIScrollView 类别实现的无限滚动。

* 演示应用中使用的内容是公开可用的,由 hn.algolia.com 和 Flickr 提供。两者都可能包含不适宜的内容。

方法调配 (Swizzling)

请注意,此类别在 UIScrollView调配 (swizzles)setContentOffsetsetContentSize 方法。

Swift Package Manager

使用 github 仓库 URL 添加新的包

https://github.com/pronebird/UIScrollView-InfiniteScroll

然后在源代码中导入模块

import UIScrollView_InfiniteScroll

CocoaPods

在你的 Podfile 中添加以下行

pod 'UIScrollView-InfiniteScroll', '~> 1.3.0'

Objective-C

#import <UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h>

或者如果使用模块

@import UIScrollView_InfiniteScroll;

Swift

在你的桥接头文件中添加以下行

#import <UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h>

Carthage

在你的 Cartfile 中添加以下行

github "pronebird/UIScrollView-InfiniteScroll" ~> 1.3.0

Objective-C

#import <UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h>

或者如果使用模块

@import UIScrollView_InfiniteScroll;

Swift

import UIScrollView_InfiniteScroll

示例

此组件带有一个用 Swift 编写的示例应用。

如果你使用 CocoaPods,你可以通过运行以下命令尝试它

pod try UIScrollView-InfiniteScroll

文档

http://pronebird.github.io/UIScrollView-InfiniteScroll/

基础

为了启用无限滚动,你必须使用 addInfiniteScrollWithHandler 提供一个处理 block。你提供的 block 在每次无限滚动组件检测到需要提供更多数据时执行。

处理 block 的目的是执行异步任务,通常是网络或数据库获取,并更新你的滚动视图或滚动视图子类。

该 block 本身在主队列上调用,因此请确保将任何长时间运行的任务移至后台队列。一旦你收到新数据,通过添加新行和 section 来更新 table view,然后调用 finishInfiniteScroll 来完成无限滚动动画并重置无限滚动组件的状态。

viewDidLoad 是安装处理 block 的好地方。

确保与 UIKit 的任何交互或 Infinite Scroll 提供的方法都在主队列上发生。在 Objective-C 中使用 dispatch_async(dispatch_get_main_queue, { ... }) 或在 Swift 中使用 DispatchQueue.main.async { ... } 在主队列上运行 UI 相关调用。

许多人犯的错误是在处理 block 中使用对 table view 或 collection view 的外部引用。不要这样做。这会创建循环引用。相反,请使用作为第一个参数传递给处理 block 的滚动视图或滚动视图子类的实例。

Objective-C

// setup infinite scroll
[tableView addInfiniteScrollWithHandler:^(UITableView* tableView) {
    // update table view
    
    // finish infinite scroll animation
    [tableView finishInfiniteScroll];
}];

Swift

tableView.addInfiniteScroll { (tableView) -> Void in
    // update table view
            
    // finish infinite scroll animation
    tableView.finishInfiniteScroll()
}

Collection view 的怪癖

UICollectionView.reloadData 会导致 contentOffset 重置。尽可能使用 UICollectionView.performBatchUpdates

Objective-C

[self.collectionView addInfiniteScrollWithHandler:^(UICollectionView* collectionView) {    
    [collectionView performBatchUpdates:^{
        // update collection view
    } completion:^(BOOL finished) {
        // finish infinite scroll animations
        [collectionView finishInfiniteScroll];
    }];
}];

Swift

collectionView.addInfiniteScroll { (collectionView) -> Void in
    collectionView.performBatchUpdates({ () -> Void in
        // update collection view
    }, completion: { (finished) -> Void in
        // finish infinite scroll animations
        collectionView.finishInfiniteScroll()
    });
}

以编程方式启动无限滚动

你可以重用无限滚动流程来加载初始数据或使用 beginInfiniteScroll(forceScroll) 获取更多数据。 viewDidLoad 是加载初始数据的好地方,但这完全取决于你。

forceScroll 参数为 true 时,Infinite Scroll 组件将尝试向下滚动以显示指示器视图。请记住,如果用户正在与滚动视图交互,则滚动不会发生。

Objective-C

[self.tableView beginInfiniteScroll:YES];

Swift

tableView.beginInfiniteScroll(true)

阻止无限滚动

有时你需要阻止无限滚动继续。例如,如果你的搜索 API 没有更多结果,则继续发出请求或显示 spinner 没有意义。

Objective-C

[tableView setShouldShowInfiniteScrollHandler:^BOOL (UITableView *tableView) {
    // Only show up to 5 pages then prevent the infinite scroll
    return (weakSelf.currentPage < 5);
}];

Swift

// Provide a block to be called right before a infinite scroll event is triggered. 
// Return YES to allow or NO to prevent it from triggering.
tableView.setShouldShowInfiniteScrollHandler { _ -> Bool in
    // Only show up to 5 pages then prevent the infinite scroll
    return currentPage < 5 
}

无缝预加载内容

理想情况下,你希望你的内容无缝流动,而无需显示 spinner。无限滚动提供了一个选项来指定偏移量(以点为单位),该偏移量将用于在用户到达滚动视图底部之前启动预加载器。

你每次加载的结果数量与足够大的偏移量之间的适当平衡应该为你的用户提供良好的体验。你很可能必须根据内容类型和设备尺寸制定自己的公式来组合这些因素。

// Preload more data 500pt before reaching the bottom of scroll view.
tableView.infiniteScrollTriggerOffset = 500;

自定义指示器

你可以使用自定义指示器来代替默认的 UIActivityIndicatorView

自定义指示器必须是 UIView 的子类,并实现以下方法

@objc func startAnimating()
@objc func stopAnimating()

Swift

let frame = CGRect(x: 0, y: 0, width: 24, height: 24)
tableView.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: frame)

请参阅自定义指示器视图的示例实现

目前,InfiniteScroll 直接使用指示器的 frame,因此请确保事先调整自定义指示器视图的大小。像 UIImageViewUIActivityIndicatorView 这样的视图将自动调整自身大小,因此无需为它们设置 frame。

贡献者

请参阅 CHANGES

致谢

演示应用图标由 PixelResort 提供。