天球仪 5.1.9

天球仪 5.1.9

测试已测试
语言语言 SwiftSwift
许可证 Apache 2.0
版本最后发布2020 年 12 月
SPM支持 SPM

Vladimir BurdukovSergei MikhanEugene FilipkovDmitry Duleba 维护。



天球仪 5.1.9

  • Sergei Mikhan 和 Vladimir Burdukov

天球仪

安全的类型方法来管理 UITableView 和 UICollectionView 的单元格。支持可展开、分页和异步加载的源。

Build Status CocoaPods Compatible Swift Package Manager compatible

安装

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数据源中使用。库为不同目的提供了不同类型的源(如下文所述)。因此,您的回收容器(UITableViewUICollectionView)可以进行不同类型的源配置。例如静态单元格设置的基集合视图源

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>>()

要将载入功能集成到你的代码中,你只需要做以下几步

  1. 实现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)
}
  1. 配置行为
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)
}
  1. 配置开始/停止加载进度和空视图更新的回调
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. 缺少的点数

  1. 自动差异计算
  2. 时间线加载器源(双向分页源)