Created
May 18, 2016 21:20
-
-
Save thebarndog/b3a69363d83e2279da0d750959c5a930 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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