2015-04-08 01:28:38 +03:00
//
// U I C o l l e c t i o n V i e w + R x . s w i f t
// R x C o c o a
//
// C r e a t e d b y K r u n o s l a v Z a h e r o n 4 / 2 / 1 5 .
// C o p y r i g h t ( c ) 2 0 1 5 K r u n o s l a v Z a h e r . A l l r i g h t s r e s e r v e d .
//
import Foundation
2015-04-10 02:52:51 +03:00
import RxSwift
2015-05-17 20:07:24 +03:00
import UIKit
2015-04-08 01:28:38 +03:00
// T h i s c a n n o t b e a g e n e r i c c l a s s b e c a u s e o f c o l l e c t i o n v i e w o b j c r u n t i m e t h a t c h e c k s f o r
// i m p l e m e n t e d s e l e c t o r s i n d a t a s o u r c e
2015-05-19 21:50:29 +03:00
public class RxCollectionViewDataSource : NSObject , UICollectionViewDataSource {
2015-04-08 01:28:38 +03:00
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 {
2015-04-08 01:28:38 +03:00
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 )
2015-05-23 01:01:36 +03:00
dispatchNext ( ( collectionView , indexPath . item ) , collectionViewObservers )
2015-04-08 01:28:38 +03:00
}
deinit {
if collectionViewObservers . count > 0 {
2015-05-23 01:01:36 +03:00
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. " ) ) )
2015-04-08 01:28:38 +03:00
}
}
}
// T h i s i s t h e m o s t s i m p l e ( b u t p r o b a b l y m o s t c o m m o n ) w a y o f u s i n g r x w i t h U I C o l l e c t i o n V i e w .
extension UICollectionView {
2015-05-19 21:50:29 +03:00
override func rx_createDelegate ( ) -> RxScrollViewDelegate {
return RxCollectionViewDelegate ( )
2015-04-08 01:28:38 +03:00
}
public func rx_subscribeItemsTo < E where E : AnyObject >
2015-05-19 21:50:29 +03:00
( dataSource : RxCollectionViewDataSource )
2015-04-08 01:28:38 +03:00
( 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 {
2015-04-08 01:28:38 +03:00
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 ) :
2015-05-02 20:27:23 +03:00
#if DEBUG
2015-05-01 23:04:41 +03:00
rxFatalError ( " Something went wrong: \( error ) " )
2015-05-02 20:27:23 +03:00
#endif
break
2015-05-01 23:04:41 +03:00
case . Completed :
break
2015-04-08 01:28:38 +03:00
}
2015-05-01 23:04:41 +03:00
} )
return CompositeDisposable ( clearDataSource , disposable )
2015-04-08 01:28:38 +03:00
}
2015-05-01 23:04:41 +03:00
2015-04-08 01:28:38 +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-04-08 01:28:38 +03:00
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 )
2015-04-08 01:28:38 +03:00
}
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-04-08 01:28:38 +03:00
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-04-08 01:28:38 +03:00
2015-05-19 21:50:29 +03:00
return cell
}
return self . rx_subscribeItemsTo ( dataSource ) ( source : source )
2015-04-08 01:28:38 +03:00
}
2015-04-20 23:56:32 +03:00
public func rx_itemTap ( ) -> Observable < ( UICollectionView , Int ) > {
2015-04-08 01:28:38 +03:00
_ = rx_checkCollectionViewDelegate ( )
return AnonymousObservable { observer in
2015-05-19 21:50:29 +03:00
MainScheduler . ensureExecutingOnScheduler ( )
2015-04-08 01:28:38 +03:00
var maybeDelegate = self . rx_checkCollectionViewDelegate ( )
if maybeDelegate = = nil {
2015-05-19 21:50:29 +03:00
let delegate = self . rx_createDelegate ( ) as ! RxCollectionViewDelegate
2015-04-08 01:28:38 +03:00
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 ( )
2015-04-08 01:28:38 +03:00
_ = self . rx_checkCollectionViewDelegate ( )
delegate . removeCollectionViewObserver ( key )
if delegate . collectionViewObservers . count = = 0 {
self . delegate = nil
}
2015-05-01 23:04:41 +03:00
}
2015-04-08 01:28:38 +03:00
}
}
2015-04-20 23:56:32 +03:00
public func rx_elementTap < E > ( ) -> Observable < E > {
2015-04-08 01:28:38 +03:00
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 ( )
2015-04-08 01:28:38 +03:00
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
}
}
// p r i v a t e m e t h o d s
2015-05-19 21:50:29 +03:00
private func rx_collectionViewDataSource ( ) -> RxCollectionViewDataSource ? {
2015-04-08 01:28:38 +03:00
MainScheduler . ensureExecutingOnScheduler ( )
if self . dataSource = = nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDataSource = self . dataSource as ? RxCollectionViewDataSource
2015-04-08 01:28:38 +03:00
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 ? {
2015-04-08 01:28:38 +03:00
MainScheduler . ensureExecutingOnScheduler ( )
if self . dataSource = = nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDataSource = self . dataSource as ? RxCollectionViewDataSource
2015-04-08 01:28:38 +03:00
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 ? {
2015-04-08 01:28:38 +03:00
MainScheduler . ensureExecutingOnScheduler ( )
if self . delegate = = nil {
return nil
}
2015-05-19 21:50:29 +03:00
let maybeDelegate = self . delegate as ? RxCollectionViewDelegate
2015-04-08 01:28:38 +03:00
if maybeDelegate = = nil {
rxFatalError ( " View already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration. " )
}
return maybeDelegate !
}
}