DataSources
  • October 10, 2023

⚠️ The latest updates is this PR. It changes the difference algorithm to DifferenceKit.

💾 🔜📱 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.

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

  1. 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)
  1. 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.
)

  1. Bind Models to SectionDataController in ViewController

var models: [Model] = […] {
didSet {
sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: {
// Completed update
})
}
}

  1. 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)

  1. Define DataController in ViewController

let collectionView: UICollectionView
var models: [Model]

let dataController = DataController<CollectionViewAdapter>(adapter: CollectionViewAdapter(collectionView: self.collectionView))

  1. 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 })

  1. Add Section to DataController

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

  1. 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: {

})
}
}

  1. 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.

  1. Reorder items of UI
  2. Call SectionDataController.reserveMoved(...
  3. Reorder items of Array
  4. 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.

  1. Clone repository.

$ git clone https://github.com/muukii/DataSources.git
$ cd DataSources
$ pod install

  1. Open xcworkspace
  2. Run DataSourcesDemo on iPhone Simulator.

Installation


CocoaPods

pod ‘DataSources’

Carthage

 

github “muukii/DataSources”

You need to add DataSources.framework and DifferenceKit.framework to your project.

GitHub


View Github

#collectionview #datadriven #datasource #diff #ios #rxswift #uicollectionview #viewcontroller
YOU MIGHT ALSO LIKE...
CameraBackground

Features Both front and back camera supported. Flash modes: auto, on, off. Countdown timer. Tap to focus. Pinch to zoom. Usage  

DKCamera

Description A light weight & simple & easy camera for iOS by Swift. It uses CoreMotion framework to detect device orientation, so ...

HorizonSDK-iOS

Horizon SDK is a state of the art real-time video recording / photo shooting iOS library. Some of the features ...

LLSimpleCamera

LLSimpleCamera: A simple customizable camera - video recorder control LLSimpleCamera is a library for creating a customized camera - video ...

RSBarcodes_Swift

RSBarcodes allows you to read 1D and 2D barcodes using the metadata scanning capabilities introduced with iOS 7 and generate ...