数据源 0.8.0

数据源 0.8.0

测试测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2019 年 4 月
SPM支持 SPM

muukiimuukii 维护。



数据源 0.8.0

数据源

💾 🔜📱类型安全的基于数据驱动的列表 UI 框架。(我们还可以使用 ASCollectionNode)

UICollectionView/UITableView 的部分更新(插入、删除、移动)对于精美的 UI 是重要的事情。
但是,数据和 UI 的同步很难。
数据源 将解决这个问题。

CI Status Version License Platform Carthage compatible FOSSA Status

谢谢

差异算法

特性

  • 数据驱动更新
    • 数据发生变化时,将会显示。
  • 部分更新,不再调用 reloadData
    • 流畅且更快。
    • 如果变化的数量超过 300,将以非动画方式更新。
  • 使用简单
  • 我们可以使用不同类型的每个部分。
  • 类型安全
    • 我们可以通过 IndexPath 获取明确类型的对象。
  • 使用适配器模式进行列表 UI 的开发
    • 例如,我们还可以将其用于 Texture 的 ASCollectionNode。(示例应用程序包含它)
  • 通过 UI 操作进行排序
  • 这个库不支持在部分之间移动。

需求

  • Swift 4
  • iOS 9+

用法(示例)

遵循协议 Diffable

public protocol Diffable {
  associatedtype Identifier : Hashable
  var diffIdentifier: Identifier { get }
}
struct Model : Diffable {

  var diffIdentifier: String {
    return id
  }
  
  let id: String
}

🤠最简单用法

  1. 在 ViewController 中定义 SectionDataController
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

😎半手动

单区域(UICollectionView)

  1. 在 ViewController 中定义 SectionDataController
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. 在 ViewController 中将模型绑定到 SectionDataController
var models: [Model] = [] {
  didSet {
    sectionDataController.update(items: items, updateMode: .partial(animated: true), completion: {
      // Completed update
    })
  }
}
  1. 在 ViewController 中实现 UICollectionViewDataSource
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
}

多区域(UICollectionView)

  1. 在 ViewController 中定义 DataController
let collectionView: UICollectionView
var models: [Model]

let dataController = DataController<CollectionViewAdapter>(adapter: CollectionViewAdapter(collectionView: self.collectionView))
  1. 在 ViewController 中定义 Section<T>
let section0 = Section(ModelA.self, isEqual: { $0.id == $1.id })
let section1 = Section(ModelB.self, isEqual: { $0.id == $1.id })
  1. Section 添加到 DataController

部分的顺序将按照添加的顺序确定。

dataController.add(section: section0) // will be 0 of section
dataController.add(section: section1) // will be 1 of section
  1. 将模型绑定到 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. 实现 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()
  }
   */
}

通过用户界面操作重新排序

SectionDataController列表-UI 提供快照。它有助于在安全中进行批量更新列表-UI。

然而,快照包含副作用。例如,如果我们通过UI操作重新排列了List-UI的项目。在这种情况下,List-UI的项目会导致快照出现差异。这将会引起不必要的差异。

因此,当我们重新排列项目时,我们应该按照以下操作进行。

  1. 重新排列UI中的项目
  2. 调用 SectionDataController.reserveMoved(...
  3. 重新排列数组中的项目
  4. 调用 SectionDataController.update(items: [T]..

附录

与RxSwift结合使用

我们可以使用与RxSwift集成的DataControllers。以下代码是一个示例。

添加扩展

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。您可以触摸数据源。

  1. 克隆仓库。
$ git clone https://github.com/muukii/DataSources.git
$ cd DataSources
$ pod install
  1. 打开xcworkspace
  2. 在iPhone模拟器上运行DataSourcesDemo

安装

CocoaPods

pod 'DataSources'

Carthage

github "muukii/DataSources"

您需要将DataSources.frameworkListDiff.framework添加到您的项目中。

作者

muukii,[email protected]https://muukii.me/

许可证

数据来源可在MIT许可证下获得。有关更多信息,请参阅LICENSE文件。

FOSSA Status