๐Ÿ’พ ๐Ÿ”œ๐Ÿ“ฑ Type-safe data-driven CollectionView, TableView Framework. (We can also use ASCollectionNode)

Overview

โš ๏ธ The latest updates is this PR. It changes the difference algorithm to DifferenceKit.

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.

CI Status Version License Platform Carthage compatible FOSSA Status

Thanks

Diff-algorithm

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

FOSSA Status

Comments
  •  _dataSource.numberOfItems(in: section) always return zero

    _dataSource.numberOfItems(in: section) always return zero

    Great library. thanks for sharing. Not sure where I did the mistake. it always return zero when calling _dataSource.numberOfItems(in: section) . Basically, I am trying to see if DataSources will work without using RxSwift and RxCocoa. Since the demo only use (RxSwift and RxCocoa)

    ` private lazy var _dataSource: DataSource = .init(adapter: .init(collectionView: self.collectionView))

    private let section0 = Section(ModelA.self, isEqual: { $0.identity == $1.identity })
    
    var section0Models: [ModelA] = [ModelA(identity: UUID().uuidString, title: String.randomEmoji())] {
        didSet {
            _dataSource.update(
                in: section0,
                items: section0Models,
                updateMode: .partial(animated: true),
                completion: {
                    
            })
        }
    }
    
        for _ in 0..<20 {
            _dataSource.add(section: section0)
        }
        for _ in 0..<20 {           
            section0Models.append(ModelA(identity: UUID().uuidString, title: String.randomEmoji()))
        }
    

    // then calling this below always return zero. do you know why? _dataSource.numberOfItems(in: 0)

    `

    opened by steve21124 7
  • Best way to handle Dynamic Section in DataSource.

    Best way to handle Dynamic Section in DataSource.

    the current code does not has dynamic section https://gist.github.com/steve21124/2b750015ab27df99474fd48bc435ab56

    What do you think the best way to handle dynamic section? Like adding new sections

      • Create array of sections that store sectionX (Where X = 0..n) ?
    • Create array of models that stores sectionXModels (where X = 0...n) ?

    or any other better idea?

    opened by steve21124 6
  • We should add `ListDiff.framework`, not `Diff.framework` when installed DataSources with Carthage

    We should add `ListDiff.framework`, not `Diff.framework` when installed DataSources with Carthage

    At first time, I only linked DataSources.framework. In this situation, logs below appeared at run time:

    dyld: Library not loaded: @rpath/ListDiff.framework/ListDiff
      Referenced from: /path/to/my/app/Frameworks/DataSources.framework/DataSources
      Reason: image not found
    (lldb)
    

    And then I looked README.md and found note about Diff.framework, but I could't find Diff.framework under the /Carthage/Build/iOS/ folder. I think it is ListDiff.framework. Is it right?

    $ git diff -U0
    diff --git a/README.md b/README.md
    index 33d51fe..a20961b 100644
    --- a/README.md
    +++ b/README.md
    @@ -355 +355 @@ github "muukii/DataSources"
    -You need to add `DataSources.framework` and `Diff.framework` to your project.
    +You need to add `DataSources.framework` and `ListDiff.framework` to your project.
    
    opened by koyachi 2
  • Can't install via Cocoapods

    Can't install via Cocoapods

    Mac-mini-admin-2:TestProject admin$ pod install /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/universal-darwin16/rbconfig.rb:213: warning: Insecure world writable dir /usr/local in PATH, mode 040777 Analyzing dependencies [!] Unable to find a specification for DataSources

    opened by Xopoko 2
  • Change wording to

    Change wording to "inspired" for diffing algo

    The copy in the readme is a little misleading. The diffing algo used in DataSources is taken from the one in IGListKit (which itself is implemented from a paper). Just want to clarify that IGListKit is not using the algo in DataSources (which the current copy implies).

    opened by rnystrom 2
  • Add license scan report and status

    Add license scan report and status

    Your FOSSA integration was successful! Attached in this PR is a badge and license report to track scan status in your README.

    Below are docs for integrating FOSSA license checks into your CI:

    opened by fossabot 1
  • How to make it work without using RxSwift?

    How to make it work without using RxSwift?

    Thanks again and sorry to ask simple question. how to make it work without RxSwift? https://gist.github.com/steve21124/2de1d70850014075dc518d5b3af5835b

    opened by steve21124 1
  • 0.3.0

    0.3.0

    • Add CollectionViewDataSource
    • Add CollectionViewSectionedDataSource
    • Rename SectionDataSource -> SectionDataController
    • Rename DataSource -> DataController
    opened by muukii 0
Releases(1.0.1)
Owner
Muukii
Swift & iOS - Working on creating apps and open-sourced libraries. I'm interested in Creating smooth and fancy UI, State-Management, Image Processing.
Muukii
A data-driven UICollectionView framework for building fast and flexible lists.

A data-driven UICollectionView framework for building fast and flexible lists. Main Features ?? Never call performBatchUpdates(_:, completion:) or rel

Instagram 12.5k Jan 1, 2023
Easy way to integrate pagination with dummy views in CollectionView, make Instagram "Discover" within minutes.

AZCollectionView Controller Features Automatic pagination handling No more awkward empty CollectionView screen AZ CollectionVIew controller give you a

Afroz Zaheer 95 May 11, 2022
CollectionView - UICollectionView using UICollectionViewCompositionalLayout

CollectionView UICollectionView using UICollectionViewCompositionalLayout create

null 0 Jan 11, 2022
Custom CollectionViewLayout class for CollectionView paging mode to work properly

PagingCollectionViewLayout About How to use About โš ๏ธ Custom class, which is inherited from UICollectionViewFlowLayout, developed for properly work Col

Vladislav 2 Jan 17, 2022
A Swift mixin for reusing views easily and in a type-safe way (UITableViewCells, UICollectionViewCells, custom UIViews, ViewControllers, Storyboardsโ€ฆ)

Reusable A Swift mixin to use UITableViewCells, UICollectionViewCells and UIViewControllers in a type-safe way, without the need to manipulate their S

Olivier Halligon 2.9k Jan 3, 2023
Easy and type-safe iOS table and collection views in Swift.

Quick Start TL;DR? SimpleSource is a library that lets you populate and update table views and collection views with ease. It gives you fully typed cl

Squarespace 96 Dec 26, 2022
TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views.

TLIndexPathTools TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views. Here are some of the awesome th

SwiftKick Mobile 347 Sep 21, 2022
Generic collection view controller with external data processing

FlexibleCollectionViewController Swift library of generic collection view controller with external data processing of functionality, like determine ce

Dmytro Pylypenko 3 Jul 16, 2018
A generic small reusable components for data source implementation for UITableView/UICollectionView in Swift.

GenericDataSource A generic small reusable components for data source implementation for UITableView/UICollectionView written in Swift. Features Basic

null 132 Sep 8, 2021
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

Square 88 Jun 30, 2022
This component allows for the transfer of data items between collection views through drag and drop

Drag and Drop Collection Views Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews. Try it

Michael Michailidis 508 Dec 19, 2022
Conv smart represent UICollectionView data structure more than UIKit.

Conv Conv smart represent UICollectionView data structure more than UIKit. Easy definition for UICollectionView DataSource and Delegate methods. And C

bannzai 157 Nov 25, 2022
Conv smart represent UICollectionView data structure more than UIKit.

Conv Conv smart represent UICollectionView data structure more than UIKit. Easy definition for UICollectionView DataSource and Delegate methods. And C

bannzai 155 May 12, 2022
API-TableView-Swift - API call with URLSession and show data to TableView with swift

API-TableView-Swift API call with URLSession and show data to TableView with swi

Aman Ullah Akhand 1 Sep 3, 2022
Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

Yosuke Ishikawa 1.9k Dec 30, 2022
compiler-driven, structured, type-safe source generation. never use gyb again!

factory 2022-09-10-a factory is a structured, type-safe source generation tool. It is intended to be a replacement for (and improvement over) the gyb

taylorswift 14 Dec 8, 2022
Cellmodel-driven collectionview manager

Sapporo [] (https://github.com/Carthage/Carthage) cellmodel-driven collectionview manager Features Easy to manage your sections and cells (reset/inser

Le Van Nghia 245 Sep 20, 2022
Cellmodel-driven tableview manager

Cellmodel-driven tableview manager

Le Van Nghia 476 Nov 6, 2022
Mathias Kรถhnke 1.1k Dec 16, 2022
MemoryCache - type-safe, thread-safe memory cache class in Swift

MemoryCache is a memory cache class in swift. The MemoryCache class incorporates LRU policies, which ensure that a cache doesnโ€™t

Yusuke Morishita 74 Nov 24, 2022