|
|
@@ -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() |
|
|
}) |
|
|
} |
|
|
} |