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

     

camerakit-ios

CameraKit helps you add reliable camera to your app quickly. Our open source camera platform provides consistent capture results, service ...

HybridCamera

[video width="192" height="416" mp4="https://swiftgit.com/wp-content/uploads/2024/12/68747470733a2f2f7261776769742e636f6d2f7374796c656b69742f696d672f6d61737465722f7669645f6564697465645f325f326d622e676966.mp4"][/video]

TakeASelfie

An iOS framework that uses the front camera, detects your face and takes a selfie. This api opens the front ...

iOS-Depth-Sampler

Code examples of Depth APIs in iOS