数据源
UICollectionView/UITableView 的部分更新(插入、删除、移动)对于精美的 UI 是重要的事情。
但是,数据和 UI 的同步很难。
数据源 将解决这个问题。
谢谢
差异算法
- 受到 IGListKit/IGListDiff 的启发。
特性
- 数据驱动更新
- 数据发生变化时,将会显示。
- 部分更新,不再调用
reloadData
- 流畅且更快。
- 如果变化的数量超过 300,将以非动画方式更新。
- 使用简单
- 我们可以使用不同类型的每个部分。
- 类型安全
- 我们可以通过 IndexPath 获取明确类型的对象。
- 使用适配器模式进行列表 UI 的开发
- 例如,我们还可以将其用于 Texture 的 ASCollectionNode。(示例应用程序包含它)
- 通过 UI 操作进行排序
- 这个库不支持在部分之间移动。
需求
- Swift 4
- iOS 9+
用法(示例)
Diffable
遵循协议 public protocol Diffable {
associatedtype Identifier : Hashable
var diffIdentifier: Identifier { get }
}
struct Model : Diffable {
var diffIdentifier: String {
return id
}
let id: String
}
🤠 最简单用法
- 在 ViewController 中定义
SectionDataController
let collectionView: UICollectionView
let sectionDataController = SectionDataController<Model, CollectionViewAdapter>(
adapter: CollectionViewAdapter(collectionView: self.collectionView),
isEqual: { $0.id == $1.id } // If Model has Equatable, you can omit this closure.
)
var models: [Model] = [] {
didSet {
sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: {
// Completed update
})
}
}
let dataSource = CollectionViewDataSource(sectionDataController: sectionDataController)
dataSource.cellFactory = { _, collectionView, indexPath, model in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.label.text = model.title
return cell
}
collectionView.dataSource = dataSource
😎 半手动
单区域(UICollectionView)
- 在 ViewController 中定义
SectionDataController
let collectionView: UICollectionView
var models: [Model]
let sectionDataController = SectionDataController<Model, CollectionViewAdapter>(
adapter: CollectionViewAdapter(collectionView: self.collectionView),
isEqual: { $0.id == $1.id } // If Model has Equatable, you can omit this closure.
)
- 在 ViewController 中将模型绑定到
SectionDataController
var models: [Model] = […] {
didSet {
sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: {
// Completed update
})
}
}
- 在 ViewController 中实现 UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sectionDataController.numberOfItems()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
let m = sectionDataController.item(for: indexPath)
cell.label.text = m.title
return cell
}
多区域(UICollectionView)
- 在 ViewController 中定义
DataController
let collectionView: UICollectionView
var models: [Model]
let dataController = DataController<CollectionViewAdapter>(adapter: CollectionViewAdapter(collectionView: self.collectionView))
- 在 ViewController 中定义
Section<T>
let section0 = Section(ModelA.self, isEqual: { $0.id == $1.id })
let section1 = Section(ModelB.self, isEqual: { $0.id == $1.id })
- 将
Section
添加到DataController
部分的顺序将按照添加的顺序确定。
dataController.add(section: section0) // will be 0 of section
dataController.add(section: section1) // will be 1 of section
- 将模型绑定到 DataController
var section0Models: [ModelA] = […] {
didSet {
dataController.update(
in: section0,
items: section0Models,
updateMode: .partial(animated: true),
completion: {
})
}
}
var section1Models: [ModelA] = […] {
didSet {
dataController.update(
in: section1,
items: section1Models,
updateMode: .partial(animated: true),
completion: {
})
}
}
- 实现 UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return dataController.numberOfSections()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataController.numberOfItems(in: section)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return dataController.item(
at: indexPath,
handlers: [
.init(section: section0) { (m: ModelA) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.label.text = m.title
return cell
},
.init(section: section1) { (m: ModelB) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.label.text = m.title
return cell
},
])
/* Other way
switch indexPath.section {
case section0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
let m = _dataController.item(at: indexPath, in: section0)
cell.label.text = m.title
return cell
case section1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
let m = _dataController.item(at: indexPath, in: section1)
cell.label.text = m.title
return cell
default:
fatalError()
}
*/
}
通过用户界面操作重新排序
SectionDataController
为 列表-UI
提供快照。它有助于在安全中进行批量更新列表-UI。
然而,快照包含副作用。例如,如果我们通过UI操作重新排列了List-UI
的项目。在这种情况下,List-UI的项目会导致快照出现差异。这将会引起不必要的差异。
因此,当我们重新排列项目时,我们应该按照以下操作进行。
- 重新排列UI中的项目
- 调用
SectionDataController.reserveMoved(...
- 重新排列数组中的项目
- 调用
SectionDataController.update(items: [T]..
附录
与RxSwift结合使用
我们可以使用与RxSwift集成的DataControllers。以下代码是一个示例。
添加扩展
import RxSwift
import DataControllers
extension SectionDataController : ReactiveCompatible {}
extension Reactive where Base : SectionDataControllerType {
public func partialUpdate<
T,
Controller: ObservableType
>
(animated: Bool) -> (_ o: Source) -> Disposable where Source.E == [T], T == Base.ItemType {
weak var t = base.asSectionDataController()
return { source in
source
.observeOn(MainScheduler.instance)
.concatMap { (newItems: [T]) -> Completable in
Completable.create { o in
guard let sectionDataController = t else {
o(.completed)
return Disposables.create()
}
sectionDataController.update(items: newItems, updateMode: .partial(animated: animated), completion: {
o(.completed)
})
return Disposables.create()
}
}
.subscribe()
}
}
}
let models: Variable<[Model]>
let sectionDataController = SectionDataController<Model, CollectionViewAdapter>
models
.asDriver()
.drive(sectionDataController.rx.partialUpdate(animated: true))
演示应用程序
此仓库包含Demo-Application。您可以触摸数据源。
- 克隆仓库。
$ git clone https://github.com/muukii/DataSources.git
$ cd DataSources
$ pod install
- 打开xcworkspace
- 在iPhone模拟器上运行
DataSourcesDemo
安装
CocoaPods
pod 'DataSources'
Carthage
github "muukii/DataSources"
您需要将DataSources.framework
和ListDiff.framework
添加到您的项目中。
作者
muukii,[email protected],https://muukii.me/
许可证
数据来源可在MIT许可证下获得。有关更多信息,请参阅LICENSE文件。