- July 26, 2025
- Mins Read
⚠️ 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.
reloadData
public protocol Diffable {
associatedtype Identifier : Hashable
var diffIdentifier: Identifier { get }
}
struct Model : Diffable {
var diffIdentifier: String {
return id
}
let id: String
}
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
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.
)
SectionDataController
in ViewController
var models: [Model] = […] {
didSet {
sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: {
// Completed update
})
}
}
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
}
DataController
in ViewController
let collectionView: UICollectionView
var models: [Model]
let dataController = DataController<CollectionViewAdapter>(adapter: CollectionViewAdapter(collectionView: self.collectionView))
Section<T>
in ViewController
let section0 = Section(ModelA.self, isEqual: { $0.id == $1.id })
let section1 = Section(ModelB.self, isEqual: { $0.id == $1.id })
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
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: {
})
}
}
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
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.
SectionDataController.reserveMoved(...
SectionDataController.update(items: [T]..
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))
This repository include Demo-Application. You can touch DataSources.
$ git clone https://github.com/muukii/DataSources.git
$ cd DataSources
$ pod install
DataSourcesDemo
on iPhone Simulator.
pod ‘DataSources’
github “muukii/DataSources”
You need to add DataSources.framework
and DifferenceKit.framework
to your project.
NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. 💻 Installation 📦 Swift Package Manager Using Swift Package Manager, add ...
An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation. NavigationStack Installation ...
With SwiftUI Router you can power your SwiftUI app with path-based routing. By utilizing a path-based system, navigation in your app becomes ...
This package takes SwiftUI's familiar and powerful NavigationStack API and gives it superpowers, allowing you to use the same API not just ...