Skip to content

Instantly share code, notes, and snippets.

@thebarndog
Created May 18, 2016 21:20
Show Gist options
  • Save thebarndog/b3a69363d83e2279da0d750959c5a930 to your computer and use it in GitHub Desktop.
Save thebarndog/b3a69363d83e2279da0d750959c5a930 to your computer and use it in GitHub Desktop.
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()
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment