天球仪
安全的类型方法来管理 UITableView 和 UICollectionView 的单元格。支持可展开、分页和异步加载的源。
安装
Swift 3.2
pod 'Astrolabe', '~> 2.1'
Swift 4.0
pod 'Astrolabe', '~> 3.0'
使用
1. 入门指南
Astrolabe中的每个单元格都应该配置ViewModel。例如
struct TestViewModel {
let title: String
let color: UIColor
}
要在Astrolabe中使用单元格,您的单元格应继承自基本Astrolabe单元格。为了进行初始单元格配置,您应使用setup()
方法
class TestCollectionCell: CollectionViewCell {
let label: UILabel = {
let label = UILabel()
label.textColor = .black
return label
}()
override func setup() {
super.setup()
contentView.addSubview(label)
label.snp.remakeConstraints { make in
make.edges.equalToSuperview()
}
}
}
为了让库重用您的单元格,它必须符合Reusable
协议
extension TestCollectionCell: Reusable {
func setup(with data: TestViewModel) {
label.text = data.title
}
static func size(for data: TestViewModel, containerSize: CGSize) -> CGSize {
return CGSize(width: 64.0, height: 64.0)
}
}
此外,Reusable
协议有方法提供重用ID,该ID用于单元格类注册。但默认情况下使用类名
public extension Reusable {
static func identifier(for data: Data) -> String {
return "\(self)"
}
}
现在您的单元格可以在Astrolabe数据源中使用。库为不同目的提供了不同类型的源(如下文所述)。因此,您的回收容器(UITableView
或UICollectionView
)可以进行不同类型的源配置。例如静态单元格设置的基集合视图源
let containerView = CollectionView<CollectionViewSource>()
1. 静态使用
要连接ViewModel与可重用单元格,您应定义符合Cellable
协议的容器
typealias Cell = CollectionCell<TestCollectionCell>
1.1 Cellable
CollectionCell
是实现用于UICollectionView的Cellable
协议的实现。现在您可以创建第一个单元格
let cell: Cellable = Cell(data: TestViewModel("Test1"))
1.2 Sectionable
所有UI部分都表示为这里的Sectionable
协议。这里有一些Sectionable
实现的示例
class Section: Sectionable {}
class HeaderSection<Container, CellView: ReusableView & Reusable>: Section {}
class CustomHeaderSection<CellView: ReusableView & Reusable>: Section {}
class FooterSection<Container, CellView: ReusableView & Reusable>: Section {}
部分可以包含标题、页脚(或两者都包含)或自定义补充视图。所有部分存储表示部分内项的单元格数组。
1.3 数据源设置
应配置的单元格应打包到部分中,并提供在数据源内部。
let section: Sectionable = Section(cells: [cell])
containerView.source.sections = [section]
1.4 选择
所有单元格都有字符串标识符。该标识符可用于识别已选项目的标识符。我们有以下几种选择管理和选择类型
public enum SelectionManagement {
case none
case automatic
case manual
}
public enum SelectionBehavior {
case single, multiple
}
传统的选择管理(使用 IndexPath
)并不能总是适用,例如在异步加载数据内容时。
1.5 自定义基 UICollectionView cell 使用
有时您需要使用您自己的 UICollectionViewCell
(例如某些库)。与 Astrolabe 一起,这同样可能。您只需定义一个使用您自定义细胞 GenericCollectionViewSource
。
typealias CustomSource = GenericCollectionViewSource<MyCustomCollectionViewCell>
let containerView = CollectionView<CustomSource>()
2. 可展开的数据源
由于每个单元格都有唯一的标识符,我们可以在其基础上构建可展开的数据源。我们建立的行为实际上是一个带有无限扩展层的树状视图。以下是源代码
let containerView = TableView<TableViewExpandableSource>()
要配置子单元格的集合,您应使用特殊的可展开单元格,这些单元格当然符合 Cellable
协议,并且可以用作子单元格。
typealias Expandable = ExpandableCollectionViewCell<TestCollectionCell>
并配置子单元格
let cells: [Cellable] = [subCell1, subCell2, subCell3]
let expandable = Expandable(data: TestViewModel("root cell"), expandableCells: cells)
3. 分页器
其他两种数据源类型可用于在页面间进行导航。Astrolabe 提供了两种类型的分页器。
3.1 静态分页器
固定数量的静态创建视图控制器。实际上使用非常简单
let containerView = CollectionView<CollectionViewPagerSource>()
只需提供分页器协议实现
class PagerViewController: UIViewController, CollectionViewPager {
override func loadView() {
super.loadView()
source.pager = self
source.reloadData()
}
var pages: [Page] {
return [
Page(controller: TableSourceViewController(), id: "source"),
Page(controller: TableLoaderSourceViewController(), id: "loader"),
Page(controller: TableStyledSourceViewController(), id: "styled")
]
}
所有单元格将自动由dataSource创建,并且子视图控制器的所有生命周期方法将按正确顺序调用。
3.2 可复用单元格分页器
具有可复用嵌入视图控制器的动态单元格数量。实际上也非常容易使用
let containerView = CollectionView<CollectionViewReusedPagerSource>()
Item ViewController必须符合ReusedPageData
协议
class ExampleReusePagerItemViewController: UIViewController, ReusedPageData {
var data: Int? {
didSet {
// TODO:
}
}
}
只需在源中为section变量提供一个单元格集合
typealias CellView = ReusedPagerCollectionViewCell<ExampleReusePagerItemViewController>
typealias Cell = CollectionCell<CellView>
let cells: [Cellable] = data.map { Cell(data: $0) }
source.sections = [Section(cells: cells)]
4. 载入装饰器
Astrolabe提供了一种在回收容器中加载异步内容的好方法。由于你的内容可以用不同的DataSource使用,我们提供了 LoaderDecoratorSource
,它会包装目标DataSource并提供相同的接口。例如
let containerView = CollectionView<LoaderDecoratorSource<CollectionViewSource>>()
要将载入功能集成到你的代码中,你只需要做以下几步
- 实现
Loader
协议
extension BasicDataExampleCollectionViewController: Loader {
func performLoading(intent: LoaderIntent) -> SectionObservable? {
// ....
return SectionObservable.just([Section(cells: cells, page: 0)])
.delay(1.0, scheduler: MainScheduler.instance)
}
}
根据LoaderIntent
返回必要的可观察对象。以下是支持的意图列表
public enum LoaderIntent {
case initial
case appearance
case force(keepData: Bool)
case pullToRefresh
case autoupdate
case page(page: Int)
}
- 配置行为
containerView.source.loadingBehavior = [.initial, .paging, .autoupdate]
目前Astrolabe提供了以下列表的加载行为
public struct LoadingBehavior: OptionSet {
public static let initial = LoadingBehavior(rawValue: 1 << 0)
public static let appearance = LoadingBehavior(rawValue: 1 << 1)
public static let autoupdate = LoadingBehavior(rawValue: 1 << 2)
public static let autoupdateBackground = LoadingBehavior(rawValue: 3 << 2)
public static let paging = LoadingBehavior(rawValue: 1 << 5)
}
- 配置开始/停止加载进度和空视图更新的回调
containerView.source.startProgress = {
// ..
}
containerView.source.stopProgress = {
// ..
}
containerView.source.updateEmptyView = {
// ..
}
5. 载入器
Astrolabe是Gnomon最好的朋友:)
要使用Gnomon将REST API上的内容加载得更简单,我们提供了特殊的类,称为Loader
,它连接Astrolabe装饰器和Gnomon请求。以下是一个简单的平面载入器协议的例子
public protocol PLoader: class {
// Result type
associatedtype PLResult: OptionalResult
// Return configured request
func request(for loadingIntent: LoaderIntent) throws -> Request<PLResult>
// Map response model into array of sections(called in background thread)
func sections(from result: PLResult, loadingIntent: LoaderIntent) -> [Sectionable]?
// Additional after setup(called in main thread)
func didReceive(result: PLResult, loadingIntent: LoaderIntent)
}
然后在LoaderProtocol
实现中,只需返回
func performLoading(intent: LoaderIntent) -> SectionObservable? {
// ....
return Astrolabe.load(pLoader: loader, intent: intent)
}
0. 缺少的点数
- 自动差异计算
- 时间线加载器源(双向分页源)