Stapler 是一个用于 iOS 的 Swift 微框架,它可以 封装获取和刷新分页数据的所有逻辑。Stapler 执行必要的后端请求,并提供一个可以用于 UI 绑定的准备好了的反应式数据源。
不再需要重新发明轮子 – Stapler 将在下一次您需要在集合视图中显示分页数据时为您节省大量时间。
请注意,Stapler 严重依赖于 ReactiveSwift。您可能还希望使用 ReactiveCocoa 以便轻松进行 UI 绑定。您应该熟悉这两个框架,尽管您可以使用自己的反应式扩展来替换(或与)ReactiveCocoa。
安装
Carthage
如果您使用 Carthage 来管理您的依赖项,只需将 Stapler 添加到您的 Cartfile
github "neekeetab/Stapler" ~> 0.1
如果您使用 Carthage 来构建您的依赖项,请确保您已经将 Stapler.framework
、ReactiveSwift.framework
、ReactiveCocoa.framework
和 Result.framework
添加到目标中 "Linked Frameworks and Libraries" 部分,并且在 Carthage 框架复制构建阶段中包含它们。
CocoaPods
如果您使用 CocoaPods 来管理您的依赖项,只需将 Stapler 添加到您的 Podfile
pod 'Stapler', '~> 0.1'
Git 子模块
- 将 Stapler 仓库添加为您的应用程序仓库的子模块。
- 在
Stapler
文件夹内部运行git submodule update --init --recursive
。 - 将
Stapler.xcodeproj
,Carthage/Checkouts/Result/Result.xcodeproj
,Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj
和Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa.xcodeproj
拖放到您的应用程序的 Xcode 项目或工作区中。 - 在您的应用程序目标设置中的“通用”标签页上,将
Stapler.framework
,ReactiveSwift.framework
,ReactiveCocoa.framework
和Result.framework
添加到“嵌入的二进制文件”部分。 - 如果您的应用程序目标完全不包含 Swift 代码,您还应该将
EMBEDDED_CONTENT_CONTAINS_SWIFT
构建设置设置为“是”。
使用方法
Stapler 使得分页处理变得简单。以下是使用它的方法:
PaginatedResponse
协议
1. 使您的分页服务器响应与 PaginatedResponse
协议相匹配。
/**
Protocol to conform data structures representing paginated server responses to.
*/
public protocol PaginatedResponse {
associatedtype Item
var items: [Item] { get }
var total: UInt { get }
}
2. 视图模型
在视图模型中(或在不遵循 MVVM 的其他地方),初始化 Stapler
,提供一个表示网络请求的 SignalProducer
。
/* Suppose we have this declared somewhere in the project
class NetworkService {
//...
struct ItemsResponse: PaginatedResponse {
let total: UInt
let items: Item
}
/// Sends ItemsResponse and completes on success, Error otherwise.
static func items(offset: UInt, size: UInt) -> SignalProducer<ItemsResponse, Error>
}
*/
class ViewModel {
let stapler = Stapler(pageSize: 10) { offset, size in
// return network request with PaginatedResponse as a value type
NetworkService.items(offset: offset, size: size)
}
}
注意!对于一个视图模型,这只需要 3 行代码!👌
3. 视图
现在剩下的就是将视图模型中的stapler
绑定到你的视图上,这通常包含一个UICollectionView
子类。你可以在示例项目部分找到一个如何操作的方法。
公开接口
Stapler
聚合了将你的UI进行绑定的所有必需组件。以下是你可以获得的内容
/**
Contains all the logic for fetching and refreshing paginated data. Provides
reactive data source to bind your UI to. Standardizes the process of getting
paginated content and (arguably) makes your life (a little bit) easier.
*/
open class Stapler<Response: PaginatedResponse, ResponseError: Error> {
/**
An action to perform on initial load (usually in viewDidLoad). Is
convenient in case you need yet another loading indicator for the initial
loading. If you don't need that, it's safe to start initial load with the
refresh action below.
*/
public let initialLoadAction: Action<(), (), ResponseError>
/**
An action to perform to reload the content. Loads first page only. To load
the rest call loadNextPageIfNeeded().
*/
public let refreshAction: Action<(), (), ResponseError>
/**
Property containing deserialized elements obtained from paginated server
responses.
*/
public let items: MutableProperty<[Response.Item]>
/**
Property containing the number of pages loaded. Initially is 0.
*/
public let pages: MutableProperty<UInt>
/**
Property containing the total number of elements on the backend size.
Initially is 0.
*/
public let total: MutableProperty<UInt>
/**
Property that tells you wheather you should show an activity indicator for
pages being loaded after the first page is loaded. Initially is false.
Becomes true at the start of 2nd page loading and stays true until all data
is loaded (no pages left). Note, that you can obtain execution status for
the first page with startInitialLoadingAction.isRefreshing if it's an
initial load or refreshAction.isRefreshing if it's a load after
refreshing.
*/
public let shouldShowNextPageActivityIndicator: Property<Bool>
/**
Signal that notifies about errors that appeared
while loading 2nd page and further. Note, that you can access errors for
the first page with startInitialLoadingAction.errors if it's an initial
load or refreshAction.errors if it's a load after refreshing.
*/
public let errors2ndPageAndLater: Signal<ResponseError, NoError>
/**
Loads next page if needed. Usually you should call this function when the
last cell in a collection view becomes visible. It's safe to call
loadNextPageIfNeeded() many times subsequently – if there's already a page
being loaded, all of these calls will be ignored. If there are no pages left,
calling this function will have no effect.
*/
public func loadNextPageIfNeeded()
/**
- parameters:
- pageSize: Number of elements per page.
- request: A closure to provide request to your backend for given offset
and size.
*/
public init(pageSize: UInt,
request: @escaping (_ offset: UInt, _ size: UInt) -> SignalProducer<Response, ResponseError>)
}
示例项目
为了更好地理解Stapler
的工作方式,请查看示例项目。以下是运行它的步骤
- 克隆
Stapler
仓库。 - 从以下终端命令中,在
Stapler
项目根目录使用其中一个获取项目依赖git submodule update --init --recursive
或,如果你已经安装了Carthagecarthage checkout
- 打开
Stapler.xcworkspace
- 构建
Result-iOS
方案 - 构建
ReactiveSwift-iOS
方案 - 构建
ReactiveCocoa-iOS
方案 - 构建
Stapler
方案 - 运行
StaplerDemo
目标
限制
在其当前实现中,Stapler
仅支持基于偏移量的分页。因此,你的后端有两条要求
- 请求必须将
偏移量
和页大小
作为参数 - 每个响应必须包含
总元素数量
有什么缺失的?
请随意请求它!
有问题吗?
请随意创建一个github问题!