GitHub - muukii/DataSources: ? ?? Type-safe data-driven CollectionView, TableVie...
source link: https://github.com/muukii/DataSources
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.md
DataSources
??? Type-safe data-driven List-UI Framework. (We can also use ASCollectionNode)
Partial updates(insert, delete, move) of UICollectionView/UITableView is important things for fancy UI.
But, It's hard that synchronous of data and UI.
DataSources will solve this problem.
Thanks
Diff-algorithm
- Inspired by IGListKit/IGListDiff.
Features
- Data driven update
- Data did change, then will display.
- Partial updates, no more calling
reloadData
- Smooth and Faster.
- if the count of changes larger than 300, update with non-animation.
- Simplified usage
- We can use different type each section.
- Type-safe
- We can take clearly typed object by IndexPath.
- Using Adapter-pattern for List-UI
- For example, We can also use this for ASCollectionNode of Texture. (Demo app includes it)
- Reorder by UI operation
- This library is not supported moving between section.
Requirements
- Swift 4
- iOS 9+
Usage (Example)
Conform protocol Diffable
public protocol Diffable { associatedtype Identifier : Hashable var diffIdentifier: Identifier { get } }
struct Model : Diffable { var diffIdentifier: String { return id } let id: String }
? Most Simplified Usage
- Define
SectionDataController
in ViewController
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
? Semi Manual
Single-Section (UICollectionView)
- Define
SectionDataController
in ViewController
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. )
- Bind Models to
SectionDataController
in ViewController
var models: [Model] = […] { didSet { sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: { // Completed update }) } }
- Implement UICollectionViewDataSource in ViewController
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 }
Multiple-Section (UICollectionView)
- Define
DataController
in ViewController
let collectionView: UICollectionView var models: [Model] let dataController = DataController<CollectionViewAdapter>(adapter: CollectionViewAdapter(collectionView: self.collectionView))
- Define
Section<T>
in ViewController
let section0 = Section(ModelA.self, isEqual: { $0.id == $1.id }) let section1 = Section(ModelB.self, isEqual: { $0.id == $1.id })
- Add
Section
toDataController
Order of Section will be decided in the order of addition.
dataController.add(section: section0) // will be 0 of section dataController.add(section: section1) // will be 1 of section
- Bind Models to 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: { }) } }
- Implement 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() } */ }
Reorder by UI operation
SectionDataController
has a snapshot for List-UI
.
It helps that perform batch update List-UI in safety.
But, the snapshots include side-effects.
For example, if we did reorder items of List-UI
by UI operation.
In this time, Items of List-UI is caused differences to the snapshot.
It will be caused unnecessary diff.
Therefore when we reorder items, we should operation followings.
- Reorder items of UI
- Call
SectionDataController.reserveMoved(...
- Reorder items of Array
- Call
SectionDataController.update(items: [T]..
Appendix
Combination with RxSwift
We can use DataControllers with RxSwift. The following code is an example.
Add extension
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
This repository include Demo-Application. You can touch DataSources.
- Clone repository.
$ git clone https://github.com/muukii/DataSources.git
$ cd DataSources
$ pod install
- Open xcworkspace
- Run
DataSourcesDemo
on iPhone Simulator.
Installation
CocoaPods
pod 'DataSources'
Carthage
github "muukii/DataSources"
You need to add DataSources.framework
and ListDiff.framework
to your project.
Author
muukii, [email protected], https://muukii.me/
License
DataSources is available under the MIT license. See the LICENSE file for more info.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK