RxSwift/RxCocoa/RxCocoa/iOS/UICollectionView+Rx.swift

262 lines
8.6 KiB
Swift
Raw Normal View History

//
// UICollectionView+Rx.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 4/2/15.
// Copyright (c) 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
2015-04-10 02:52:51 +03:00
import RxSwift
import UIKit
// This cannot be a generic class because of collection view objc runtime that checks for
// implemented selectors in data source
2015-05-19 21:50:29 +03:00
public class RxCollectionViewDataSource : NSObject, UICollectionViewDataSource {
public typealias CellFactory = (UICollectionView, NSIndexPath, AnyObject) -> UICollectionViewCell
public var items: [AnyObject] {
get {
return _items
}
}
var _items: [AnyObject]
let cellFactory: CellFactory
public init(cellFactory: CellFactory) {
self._items = []
self.cellFactory = cellFactory
}
public func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return _items.count
}
public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.item < _items.count {
return cellFactory(collectionView, indexPath, self._items[indexPath.item])
}
else {
rxFatalError("something went wrong")
let cell: UICollectionViewCell? = nil
return cell!
}
}
}
2015-05-19 21:50:29 +03:00
public class RxCollectionViewDelegate: RxScrollViewDelegate, UICollectionViewDelegate {
public typealias Observer = ObserverOf<(UICollectionView, Int)>
public typealias DisposeKey = Bag<Observer>.KeyType
var collectionViewObservers: Bag<Observer>
override public init() {
collectionViewObservers = Bag()
}
public func addCollectionViewObserver(observer: Observer) -> DisposeKey {
MainScheduler.ensureExecutingOnScheduler()
return collectionViewObservers.put(observer)
}
public func removeCollectionViewObserver(key: DisposeKey) {
MainScheduler.ensureExecutingOnScheduler()
let element = collectionViewObservers.removeKey(key)
if element == nil {
removingObserverFailed()
}
}
public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
collectionView.deselectItemAtIndexPath(indexPath, animated: true)
dispatchNext((collectionView, indexPath.item), collectionViewObservers)
}
deinit {
if collectionViewObservers.count > 0 {
handleVoidObserverResult(failure(rxError(RxCocoaError.InvalidOperation, "Something went wrong. Deallocating collection view delegate while there are still subscribed observers means that some subscription was left undisposed.")))
}
}
}
// This is the most simple (but probably most common) way of using rx with UICollectionView.
extension UICollectionView {
2015-05-19 21:50:29 +03:00
override func rx_createDelegate() -> RxScrollViewDelegate {
return RxCollectionViewDelegate()
}
public func rx_subscribeItemsTo<E where E: AnyObject>
2015-05-19 21:50:29 +03:00
(dataSource: RxCollectionViewDataSource)
(source: Observable<[E]>)
2015-05-01 23:04:41 +03:00
-> Disposable {
MainScheduler.ensureExecutingOnScheduler()
if self.dataSource != nil && self.dataSource !== dataSource {
rxFatalError("Data source is different")
}
self.dataSource = dataSource
let clearDataSource = AnonymousDisposable {
if self.dataSource != nil && self.dataSource !== dataSource {
rxFatalError("Data source is different")
}
2015-05-01 23:04:41 +03:00
self.dataSource = nil
}
let disposable = source.subscribe(AnonymousObserver { event in
2015-05-19 21:50:29 +03:00
MainScheduler.ensureExecutingOnScheduler()
2015-05-01 23:04:41 +03:00
switch event {
case .Next(let boxedValue):
let value = boxedValue.value
dataSource._items = value
self.reloadData()
case .Error(let error):
#if DEBUG
2015-05-01 23:04:41 +03:00
rxFatalError("Something went wrong: \(error)")
#endif
break
2015-05-01 23:04:41 +03:00
case .Completed:
break
}
2015-05-01 23:04:41 +03:00
})
return CompositeDisposable(clearDataSource, disposable)
}
2015-05-01 23:04:41 +03:00
public func rx_subscribeItemsTo<E where E : AnyObject>
(cellFactory: (UICollectionView, NSIndexPath, E) -> UICollectionViewCell)
(source: Observable<[E]>)
2015-05-01 23:04:41 +03:00
-> Disposable {
2015-05-19 21:50:29 +03:00
let dataSource = RxCollectionViewDataSource(cellFactory: {
cellFactory($0, $1, $2 as! E)
})
return self.rx_subscribeItemsTo(dataSource)(source: source)
}
public func rx_subscribeItemsWithIdentifierTo<E, Cell where E : AnyObject, Cell : UICollectionViewCell>
(cellIdentifier: String, configureCell: (UICollectionView, NSIndexPath, E, Cell) -> Void)
(source: Observable<[E]>)
2015-05-01 23:04:41 +03:00
-> Disposable {
2015-05-19 21:50:29 +03:00
let dataSource = RxCollectionViewDataSource {
let cell = $0.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: $1) as! Cell
configureCell($0, $1, $2 as! E, cell)
2015-05-19 21:50:29 +03:00
return cell
}
return self.rx_subscribeItemsTo(dataSource)(source: source)
}
2015-04-20 23:56:32 +03:00
public func rx_itemTap() -> Observable<(UICollectionView, Int)> {
_ = rx_checkCollectionViewDelegate()
return AnonymousObservable { observer in
2015-05-19 21:50:29 +03:00
MainScheduler.ensureExecutingOnScheduler()
var maybeDelegate = self.rx_checkCollectionViewDelegate()
if maybeDelegate == nil {
2015-05-19 21:50:29 +03:00
let delegate = self.rx_createDelegate() as! RxCollectionViewDelegate
maybeDelegate = delegate
self.delegate = maybeDelegate
}
let delegate = maybeDelegate!
let key = delegate.addCollectionViewObserver(observer)
2015-05-01 23:04:41 +03:00
return AnonymousDisposable {
2015-05-19 21:50:29 +03:00
MainScheduler.ensureExecutingOnScheduler()
_ = self.rx_checkCollectionViewDelegate()
delegate.removeCollectionViewObserver(key)
if delegate.collectionViewObservers.count == 0 {
self.delegate = nil
}
2015-05-01 23:04:41 +03:00
}
}
}
2015-04-20 23:56:32 +03:00
public func rx_elementTap<E>() -> Observable<E> {
2015-04-20 23:56:32 +03:00
return rx_itemTap() >- map { (tableView, rowIndex) -> E in
2015-05-19 21:50:29 +03:00
let maybeDataSource: RxCollectionViewDataSource? = self.rx_collectionViewDataSource()
if maybeDataSource == nil {
rxFatalError("To use element tap table view needs to use table view data source. You can still use `rx_observableItemTap`.")
}
let dataSource = maybeDataSource!
return dataSource.items[rowIndex] as! E
}
}
// private methods
2015-05-19 21:50:29 +03:00
private func rx_collectionViewDataSource() -> RxCollectionViewDataSource? {
MainScheduler.ensureExecutingOnScheduler()
if self.dataSource == nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDataSource = self.dataSource as? RxCollectionViewDataSource
if maybeDataSource == nil {
rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.")
}
return maybeDataSource!
}
2015-05-19 21:50:29 +03:00
private func rx_checkCollectionViewDataSource<E>() -> RxCollectionViewDataSource? {
MainScheduler.ensureExecutingOnScheduler()
if self.dataSource == nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDataSource = self.dataSource as? RxCollectionViewDataSource
if maybeDataSource == nil {
rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.")
}
return maybeDataSource!
}
2015-05-19 21:50:29 +03:00
private func rx_checkCollectionViewDelegate() -> RxCollectionViewDelegate? {
MainScheduler.ensureExecutingOnScheduler()
if self.delegate == nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDelegate = self.delegate as? RxCollectionViewDelegate
if maybeDelegate == nil {
rxFatalError("View already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration.")
}
return maybeDelegate!
}
}