Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save speaktoalvin/275f0a81404ef4285ecda17aa22e5681 to your computer and use it in GitHub Desktop.

Select an option

Save speaktoalvin/275f0a81404ef4285ecda17aa22e5681 to your computer and use it in GitHub Desktop.

Revisions

  1. @thebarndog thebarndog created this gist May 18, 2016.
    240 changes: 240 additions & 0 deletions CollectionViewBinder.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,240 @@
    import UIKit
    import Foundation
    import ReactiveCocoa
    import Result
    import Dwifft

    /**
    Encapsulates information about collection view nib/cell registration.

    - Nib: UINib.
    - Class: UICollectionViewCell class.
    */
    private enum RegisteredType {
    case Nib(UINib)
    case Class(AnyClass)
    }

    /// CollectionViewBinder is a ReactiveCocoa helper class for simplifying using `UICollectionView`s with ReactiveCocoa.
    /// Using the binder lets the view configuration for collection view cells move to the view and out of the view controller.
    final class CollectionViewBinder<T: Equatable>: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {

    /// `UICollectionViewDelegate` object.
    weak var delegate: UICollectionViewDelegate? {
    get { return self.collectionView?.delegate }
    set { self.collectionView?.delegate = newValue }
    }

    /// `CollectionBinderDataSource` object.
    weak var dataSource: CollectionBinderDataSource? {
    willSet {
    if let _ = newValue {
    reloadData()
    }
    }
    }

    /// `UICollectionView` associated with the binder.
    private(set) weak var collectionView: UICollectionView?

    /// Signal that emits the data
    private var dataSignal: SignalProducer<[[T]], NoError>
    /// Signal that emits the supplementary data
    private var supplementarySignal: SignalProducer<[String: [T]], NoError>?

    /// Mutable data property
    private let data = MutableProperty<[[T]]>([])
    /// Mutable supplementary data property
    private let supplementaryData: MutableProperty<[String: [T]]> = MutableProperty<[String: [T]]>([:])


    // MARK: - Registration Information

    /// All the cells registered with the binder.
    private var cells: [String: RegisteredType] = [:]
    /// All the supplementary views registered with the binder.
    private var supplementaries: [String: (String, RegisteredType)] = [:]
    /// All the decoration views registered with the binder.z
    private var decorations: [String: RegisteredType] = [:]

    /**
    Create a binding helper.

    - parameter collectionView: Collection view object to bind data to.
    - parameter dataSignal: SignalProducer that emits changes to the underlying data.
    - parameter supplementarySignal: SignalProducer that emits changes to the data the represents headers, footers, etc.
    */
    init(collectionView: UICollectionView, dataSignal: SignalProducer<[[T]], NoError>, supplementarySignal: SignalProducer<[String: [T]], NoError>?) {
    self.collectionView = collectionView
    self.dataSignal = dataSignal
    self.supplementarySignal = supplementarySignal

    super.init()
    self.data <~ self.dataSignal
    self.supplementaryData <~ self.supplementarySignal ?? SignalProducer.empty
    self.collectionView?.dataSource = self

    self.data.producer.on(next: { print($0.count) }).on(next: { _ in self.reloadData() }).takeUntil(self.willDeallocSignal()).start()
    }

    // MARK: - UICollectionViewDataSource

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    guard let _ = self.dataSource where !self.cells.isEmpty else {
    return 0
    }
    return self.data.value.count
    }


    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    guard let _ = self.dataSource where !self.cells.isEmpty && section < self.data.value.count else {
    return 0
    }
    return self.data.value[section].count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    guard let dataSource = self.dataSource else {
    return UICollectionViewCell()
    }
    let model = self.data.value[indexPath.section][indexPath.item]
    if let reuseIdentifier = dataSource.collectionView(collectionView, reuseIdentifierForItemAtIndexPath: indexPath) {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
    if cell is ReactiveView {
    (cell as! ReactiveView).bindViewModel(model)
    }
    return cell
    }
    return UICollectionViewCell()
    }

    func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {

    if let model = self.supplementaryData.value[kind]?[indexPath.section],
    reuseIdentifier = self.dataSource?.collectionView(collectionView, reuseIdentifierForSupplementaryViewOfKind: kind, atIndexPath: indexPath) {
    let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: reuseIdentifier, forIndexPath: indexPath)
    if view is ReactiveView {
    (view as! ReactiveView).bindViewModel(model)
    }
    return view
    }
    return UICollectionReusableView()
    }
    }

    // MARK: - Registering Views
    extension CollectionViewBinder {

    /**
    Register a nib for a cell reuse identifier.

    - parameter nib: Nib object.
    - parameter identifier: Reuse identifier to register the specified nib with.
    */
    func registerNib(nib: UINib, forCellReuseIdentifier identifier: String) {
    self.cells[identifier] = .Nib(nib)
    self.collectionView?.registerNib(nib, forCellWithReuseIdentifier: identifier)
    }

    /**
    Register a class for a cell reuse identifier.

    - parameter aClass: Class object.
    - parameter identifier: Reuse identifier to register the specified class with.
    */
    func registerClass(aClass: AnyClass, forCellReuseIdentifier identifier: String) {
    self.cells[identifier] = .Class(aClass)
    self.collectionView?.registerClass(aClass, forCellWithReuseIdentifier: identifier)
    }

    /**
    Register a nib for a supplementary reuse identifier.

    - parameter nib: Nib object.
    - parameter kind: Supplementary element kind.
    - parameter identifier: Reuse identifier to register the specified nib with.
    */
    func registerNib(nib: UINib, forSupplementaryViewOfKind kind: String, withReuseIdentifier identifier: String) {
    self.supplementaries[kind] = (identifier, .Nib(nib))
    self.collectionView?.registerNib(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: identifier)
    }

    /**
    Register a class for a supplementary reuse identifier.

    - parameter aClass: Class object.
    - parameter kind: Supplementary element kind.
    - parameter identifier: Reuse identifier to register the specified class with.
    */
    func registerClass(aClass: AnyClass, forSupplementaryViewOfKind kind: String, withReuseIdentifier identifier: String) {
    self.supplementaries[kind] = (identifier, .Class(aClass))
    self.collectionView?.registerClass(aClass, forSupplementaryViewOfKind: kind, withReuseIdentifier: identifier)
    }

    /**
    Register a nib for a decoration kind.

    - parameter nib: Nib object.
    - parameter elementKind: Decoration identifier.
    */
    func registerNib(nib: UINib, forDecorationViewOfKind elementKind: String) {
    self.decorations[elementKind] = .Nib(nib)
    self.collectionView?.collectionViewLayout.registerNib(nib, forDecorationViewOfKind: elementKind)
    }

    /**
    Register a class for a decoration kind.

    - parameter aClass: Class object.
    - parameter elementKind: Decoration identifier.
    */
    func registerClass(aClass: AnyClass, forDecorationViewOfKind elementKind: String) {
    self.decorations[elementKind] = .Class(aClass)
    self.collectionView?.collectionViewLayout.registerClass(aClass, forDecorationViewOfKind: elementKind)
    }

    }

    // MARK: - Reloading
    extension CollectionViewBinder {
    /**
    Reload the binder.
    */
    func reloadData() {
    dispatch_async(dispatch_get_main_queue(), { [weak self] in
    self?.collectionView?.reloadData()
    })
    }

    /**
    Reload the specified sections.

    - parameter indexes: Index set of sections to reload.
    */
    func reloadSections(indexes: NSIndexSet) {
    dispatch_async(dispatch_get_main_queue(), { [weak self] in
    self?.collectionView?.reloadSections(indexes)
    })
    }

    /**
    Reload all the items at the specified index paths.

    - parameter indexPaths: List of index paths to reload.
    */
    func reloadItemsAtIndexPaths(indexPaths: [NSIndexPath]) {
    dispatch_async(dispatch_get_main_queue(), { [weak self] in
    self?.collectionView?.reloadItemsAtIndexPaths(indexPaths)
    })
    }

    /**
    Reload the input views.
    */
    func reloadInputViews() {
    dispatch_async(dispatch_get_main_queue(), { [weak self] in
    self?.collectionView?.reloadInputViews()
    })
    }
    }