From eeddf1fe5d39e4b3e577544ea88b2c38a4a6be3d Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Sun, 10 Jan 2016 20:19:30 +0100 Subject: [PATCH] Adds `SectionedViewDataSourceType` and `rx_modelDeselected`, `rx_deselectedItemAtIndexPath` for `UICollectionView`, and corresponding unit tests. --- Rx.xcodeproj/project.pbxproj | 28 ++- .../xcschemes/RxBlocking-OSX.xcscheme | 2 +- .../xcschemes/RxBlocking-iOS.xcscheme | 2 +- .../xcschemes/RxBlocking-tvOS.xcscheme | 2 +- .../xcschemes/RxBlocking-watchOS.xcscheme | 2 +- .../xcschemes/RxCocoa-OSX.xcscheme | 2 +- .../xcschemes/RxCocoa-iOS.xcscheme | 2 +- .../xcschemes/RxCocoa-tvOS.xcscheme | 2 +- .../xcschemes/RxCocoa-watchOS.xcscheme | 2 +- .../xcschemes/RxSwift-OSX.xcscheme | 2 +- .../xcschemes/RxSwift-iOS.xcscheme | 2 +- .../xcschemes/RxSwift-tvOS.xcscheme | 2 +- .../xcschemes/RxSwift-watchOS.xcscheme | 2 +- .../Common/SectionedViewDataSourceType.swift | 24 +++ ...ollectionViewReactiveArrayDataSource.swift | 21 ++- .../RxTableViewReactiveArrayDataSource.swift | 25 ++- RxCocoa/iOS/UICollectionView+Rx.swift | 67 ++++--- RxCocoa/iOS/UITableView+Rx.swift | 50 ++--- .../xcschemes/RxExample-OSX.xcscheme | 2 +- .../RxExample-iOS-no-module.xcscheme | 2 +- .../xcschemes/RxExample-iOS.xcscheme | 2 +- .../RxCocoaTests/Control+RxTests+UIKit.swift | 172 +++++++++++++++++- .../SectionedViewDataSourceMock.swift | 57 ++++++ 23 files changed, 385 insertions(+), 89 deletions(-) create mode 100644 RxCocoa/Common/SectionedViewDataSourceType.swift create mode 100644 Tests/RxCocoaTests/TestImplementations/SectionedViewDataSourceMock.swift diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index af93104a..bab1f4ee 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -373,7 +373,6 @@ C83509651C38706E0027C24C /* VirtualSchedulerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C835091C1C38706D0027C24C /* VirtualSchedulerTest.swift */; }; C83509661C38706E0027C24C /* RxTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C835091D1C38706D0027C24C /* RxTest.swift */; }; C83509671C38706E0027C24C /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C835091F1C38706E0027C24C /* Foundation+Extensions.swift */; }; - C83509681C38706E0027C24C /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C83509201C38706E0027C24C /* Info.plist */; }; C83509691C38706E0027C24C /* Recorded+Timeless.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83509211C38706E0027C24C /* Recorded+Timeless.swift */; }; C835096A1C38706E0027C24C /* TestErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83509221C38706E0027C24C /* TestErrors.swift */; }; C835096B1C38706E0027C24C /* XCTest+AllTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83509231C38706E0027C24C /* XCTest+AllTests.swift */; }; @@ -570,6 +569,8 @@ C8554E2D1C3051620052E67D /* PriorityQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8554E291C3051620052E67D /* PriorityQueue.swift */; }; C85BA0591C3878850075D68E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83508D61C38706D0027C24C /* main.swift */; }; C85BA05A1C3878870075D68E /* PerformanceTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83508D71C38706D0027C24C /* PerformanceTools.swift */; }; + C860EC951C42E25E00A664B3 /* SectionedViewDataSourceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132521C42DA7F00B59FFF /* SectionedViewDataSourceMock.swift */; }; + C860EC961C42E26100A664B3 /* SectionedViewDataSourceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132521C42DA7F00B59FFF /* SectionedViewDataSourceMock.swift */; }; C86409FC1BA593F500D3C4E8 /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86409FB1BA593F500D3C4E8 /* Range.swift */; }; C86409FD1BA593F500D3C4E8 /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86409FB1BA593F500D3C4E8 /* Range.swift */; }; C8640A031BA5B12A00D3C4E8 /* Repeat.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8640A021BA5B12A00D3C4E8 /* Repeat.swift */; }; @@ -673,6 +674,10 @@ C8C4B4C31C17727000828BD5 /* MessageSentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C4B4C01C17727000828BD5 /* MessageSentObserver.swift */; }; C8C4B4C41C17727000828BD5 /* MessageSentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C4B4C01C17727000828BD5 /* MessageSentObserver.swift */; }; C8C4B4C51C17727000828BD5 /* MessageSentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C4B4C01C17727000828BD5 /* MessageSentObserver.swift */; }; + C8D132441C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */; }; + C8D132451C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */; }; + C8D132461C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */; }; + C8D132471C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */; }; C8DB967E1BF7496C0084BD53 /* KVORepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DB967D1BF7496C0084BD53 /* KVORepresentable.swift */; }; C8DB967F1BF7496C0084BD53 /* KVORepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DB967D1BF7496C0084BD53 /* KVORepresentable.swift */; }; C8DB96801BF7496C0084BD53 /* KVORepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DB967D1BF7496C0084BD53 /* KVORepresentable.swift */; }; @@ -1601,6 +1606,8 @@ C8C4B4A71C17722400828BD5 /* _RXObjCRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _RXObjCRuntime.m; sourceTree = ""; }; C8C4B4A81C17722400828BD5 /* _RXObjCRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _RXObjCRuntime.h; sourceTree = ""; }; C8C4B4C01C17727000828BD5 /* MessageSentObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSentObserver.swift; sourceTree = ""; }; + C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedViewDataSourceType.swift; sourceTree = ""; }; + C8D132521C42DA7F00B59FFF /* SectionedViewDataSourceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedViewDataSourceMock.swift; sourceTree = ""; }; C8DB967D1BF7496C0084BD53 /* KVORepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVORepresentable.swift; sourceTree = ""; }; C8DB96821BF754C80084BD53 /* NSObject+Rx+KVORepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Rx+KVORepresentable.swift"; sourceTree = ""; }; C8DB96871BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KVORepresentable+CoreGraphics.swift"; sourceTree = ""; }; @@ -2035,6 +2042,7 @@ C8DB96871BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift */, C8DB968C1BF7595D0084BD53 /* KVORepresentable+Swift.swift */, C8BCD3F31C14B6D1005F1280 /* NSLayoutConstraint+Rx.swift */, + C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */, ); path = Common; sourceTree = ""; @@ -2149,6 +2157,7 @@ C83508D81C38706D0027C24C /* RxCocoaTests */ = { isa = PBXGroup; children = ( + C8D132511C42DA7F00B59FFF /* TestImplementations */, C83508D91C38706D0027C24C /* CLLocationManager+RxTests.swift */, 8476A01F1C3D5D580040BA22 /* UIImagePickerController+RxTests.swift */, C83508DA1C38706D0027C24C /* Control+RxTests+Cocoa.swift */, @@ -2403,6 +2412,14 @@ path = Platform; sourceTree = ""; }; + C8D132511C42DA7F00B59FFF /* TestImplementations */ = { + isa = PBXGroup; + children = ( + C8D132521C42DA7F00B59FFF /* SectionedViewDataSourceMock.swift */, + ); + path = TestImplementations; + sourceTree = ""; + }; C8E3A6FC1C25CE2200643FE6 /* RxTests */ = { isa = PBXGroup; children = ( @@ -2960,7 +2977,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0710; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Krunoslav Zaher"; TargetAttributes = { C83508C21C386F6F0027C24C = { @@ -3059,7 +3076,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C83509681C38706E0027C24C /* Info.plist in Resources */, C83509271C38706E0027C24C /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3198,6 +3214,7 @@ C882542A1B8A752B00B02D69 /* UIControl+Rx.swift in Sources */, C8F6A1451BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */, C8DB967E1BF7496C0084BD53 /* KVORepresentable.swift in Sources */, + C8D132441C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */, C849EF8B1C3195950048AC4A /* Variable+Driver.swift in Sources */, C88254341B8A752B00B02D69 /* UITableView+Rx.swift in Sources */, C88254161B8A752B00B02D69 /* RxCollectionViewReactiveArrayDataSource.swift in Sources */, @@ -3294,6 +3311,7 @@ C8093EF81B8A732E0088E94D /* NSURLSession+Rx.swift in Sources */, C80DDE9C1BCE69BA006A1832 /* Driver+Operators.swift in Sources */, C8093F4C1B8A732E0088E94D /* NSSlider+Rx.swift in Sources */, + C8D132451C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */, C8093EE81B8A732E0088E94D /* ControlTarget.swift in Sources */, C80D33901B91EF9E0014629D /* Observable+Bind.swift in Sources */, ); @@ -3387,6 +3405,7 @@ C835092C1C38706E0027C24C /* Control+RxTests+UIKit.swift in Sources */, C83509571C38706E0027C24C /* Observable+CreationTest.swift in Sources */, C83509321C38706E0027C24C /* DelegateProxyTest.swift in Sources */, + C860EC951C42E25E00A664B3 /* SectionedViewDataSourceMock.swift in Sources */, C83509431C38706E0027C24C /* MockDisposable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3457,6 +3476,7 @@ C83509D01C38752E0027C24C /* RuntimeStateSnapshot.swift in Sources */, C83509AF1C3874DC0027C24C /* RxTest.swift in Sources */, C83509F41C38755D0027C24C /* BagTest.swift in Sources */, + C860EC961C42E26100A664B3 /* SectionedViewDataSourceMock.swift in Sources */, C8350A0F1C3875630027C24C /* Observable+MultipleTest+Zip.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4080,6 +4100,7 @@ C8F0C0151BBBFBB9001B112F /* UIControl+Rx.swift in Sources */, C8F6A1481BF0B9B2007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */, C8DB96811BF7496C0084BD53 /* KVORepresentable.swift in Sources */, + C8D132471C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */, C849EF8E1C3195950048AC4A /* Variable+Driver.swift in Sources */, C8F0C0161BBBFBB9001B112F /* UITableView+Rx.swift in Sources */, C8F0C0171BBBFBB9001B112F /* RxCollectionViewReactiveArrayDataSource.swift in Sources */, @@ -4169,6 +4190,7 @@ C8F6A1471BF0B9B2007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */, C8DB96801BF7496C0084BD53 /* KVORepresentable.swift in Sources */, C849EF8D1C3195950048AC4A /* Variable+Driver.swift in Sources */, + C8D132461C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */, D203C4F31BB9C4CA00D02D00 /* RxCollectionViewReactiveArrayDataSource.swift in Sources */, D203C50B1BB9C53E00D02D00 /* UIScrollView+Rx.swift in Sources */, C8C4B4AB1C17722400828BD5 /* _RXObjCRuntime.m in Sources */, diff --git a/Rx.xcodeproj/xcshareddata/xcschemes/RxBlocking-OSX.xcscheme b/Rx.xcodeproj/xcshareddata/xcschemes/RxBlocking-OSX.xcscheme index 3cb14f30..ee387821 100644 --- a/Rx.xcodeproj/xcshareddata/xcschemes/RxBlocking-OSX.xcscheme +++ b/Rx.xcodeproj/xcshareddata/xcschemes/RxBlocking-OSX.xcscheme @@ -1,6 +1,6 @@ Any +} \ No newline at end of file diff --git a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift index 057b93fb..2c1aa325 100644 --- a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift @@ -15,7 +15,9 @@ import RxSwift #endif // objc monkey business -class _RxCollectionViewReactiveArrayDataSource: NSObject, UICollectionViewDataSource { +class _RxCollectionViewReactiveArrayDataSource + : NSObject + , UICollectionViewDataSource { func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 1 @@ -38,8 +40,9 @@ class _RxCollectionViewReactiveArrayDataSource: NSObject, UICollectionViewDataSo } } -class RxCollectionViewReactiveArrayDataSourceSequenceWrapper : RxCollectionViewReactiveArrayDataSource - , RxCollectionViewDataSourceType { +class RxCollectionViewReactiveArrayDataSourceSequenceWrapper + : RxCollectionViewReactiveArrayDataSource + , RxCollectionViewDataSourceType { typealias Element = S override init(cellFactory: CellFactory) { @@ -61,7 +64,9 @@ class RxCollectionViewReactiveArrayDataSourceSequenceWrapper : // Please take a look at `DelegateProxyType.swift` -class RxCollectionViewReactiveArrayDataSource : _RxCollectionViewReactiveArrayDataSource { +class RxCollectionViewReactiveArrayDataSource + : _RxCollectionViewReactiveArrayDataSource + , SectionedViewDataSourceType { typealias CellFactory = (UICollectionView, Int, Element) -> UICollectionViewCell @@ -70,6 +75,14 @@ class RxCollectionViewReactiveArrayDataSource : _RxCollectionViewReacti func modelAtIndex(index: Int) -> Element? { return itemModels?[index] } + + func modelAtIndexPath(indexPath: NSIndexPath) throws -> Any { + precondition(indexPath.section == 0) + guard let item = itemModels?[indexPath.item] else { + throw RxCocoaError.ItemsNotYetBound(object: self) + } + return item + } var cellFactory: CellFactory diff --git a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift index 06a6df1d..09312322 100644 --- a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift @@ -15,7 +15,9 @@ import RxSwift #endif // objc monkey business -class _RxTableViewReactiveArrayDataSource: NSObject, UITableViewDataSource { +class _RxTableViewReactiveArrayDataSource + : NSObject + , UITableViewDataSource { func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 @@ -39,14 +41,15 @@ class _RxTableViewReactiveArrayDataSource: NSObject, UITableViewDataSource { } -class RxTableViewReactiveArrayDataSourceSequenceWrapper : RxTableViewReactiveArrayDataSource - , RxTableViewDataSourceType { +class RxTableViewReactiveArrayDataSourceSequenceWrapper + : RxTableViewReactiveArrayDataSource + , RxTableViewDataSourceType { typealias Element = S override init(cellFactory: CellFactory) { super.init(cellFactory: cellFactory) } - + func tableView(tableView: UITableView, observedEvent: Event) { switch observedEvent { case .Next(let value): @@ -60,7 +63,9 @@ class RxTableViewReactiveArrayDataSourceSequenceWrapper : RxTab } // Please take a look at `DelegateProxyType.swift` -class RxTableViewReactiveArrayDataSource : _RxTableViewReactiveArrayDataSource { +class RxTableViewReactiveArrayDataSource + : _RxTableViewReactiveArrayDataSource + , SectionedViewDataSourceType { typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell var itemModels: [Element]? = nil @@ -68,7 +73,15 @@ class RxTableViewReactiveArrayDataSource : _RxTableViewReactiveArrayDat func modelAtIndex(index: Int) -> Element? { return itemModels?[index] } - + + func modelAtIndexPath(indexPath: NSIndexPath) throws -> Any { + precondition(indexPath.section == 0) + guard let item = itemModels?[indexPath.item] else { + throw RxCocoaError.ItemsNotYetBound(object: self) + } + return item + } + let cellFactory: CellFactory init(cellFactory: CellFactory) { diff --git a/RxCocoa/iOS/UICollectionView+Rx.swift b/RxCocoa/iOS/UICollectionView+Rx.swift index 6786d47d..a734105c 100644 --- a/RxCocoa/iOS/UICollectionView+Rx.swift +++ b/RxCocoa/iOS/UICollectionView+Rx.swift @@ -133,27 +133,29 @@ extension UICollectionView { return ControlEvent(events: source) } - + + /** + Reactive wrapper for `delegate` message `collectionView:didSelectItemAtIndexPath:`. + */ + public var rx_itemDeselected: ControlEvent { + let source = rx_delegate.observe("collectionView:didDeselectItemAtIndexPath:") + .map { a in + return a[1] as! NSIndexPath + } + + return ControlEvent(events: source) + } + /** Reactive wrapper for `delegate` message `collectionView:didSelectItemAtIndexPath:`. + + It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence, + or any other data source conforming to `SectionedViewDataSourceType` protocol. - It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence. - + ``` collectionView.rx_modelSelected(MyModel.self) .map { ... - - If custom data source is being bound, new `rx_modelSelected` wrapper needs to be written also. - - public func rx_myModelSelected() -> ControlEvent { - let source: Observable = rx_itemSelected.map { indexPath in - let dataSource: MyDataSource = self.rx_dataSource.forwardToDelegate() as! MyDataSource - - return dataSource.modelAtIndex(indexPath.item)! - } - - return ControlEvent(source: source) - } - + ``` */ public func rx_modelSelected(modelType: T.Type) -> ControlEvent { let source: Observable = rx_itemSelected.flatMap { [weak self] indexPath -> Observable in @@ -166,18 +168,39 @@ extension UICollectionView { return ControlEvent(events: source) } + + /** + Reactive wrapper for `delegate` message `collectionView:didSelectItemAtIndexPath:`. + + It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence, + or any other data source conforming to `SectionedViewDataSourceType` protocol. + + ``` + collectionView.rx_modelDeselected(MyModel.self) + .map { ... + ``` + */ + public func rx_modelDeselected(modelType: T.Type) -> ControlEvent { + let source: Observable = rx_itemDeselected.flatMap { [weak self] indexPath -> Observable in + guard let view = self else { + return Observable.empty() + } + + return Observable.just(try view.rx_modelAtIndexPath(indexPath)) + } + + return ControlEvent(events: source) + } /** Syncronous helper method for retrieving a model at indexPath through a reactive data source */ public func rx_modelAtIndexPath(indexPath: NSIndexPath) throws -> T { - let dataSource: RxCollectionViewReactiveArrayDataSource = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.") + let dataSource: SectionedViewDataSourceType = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.") - guard let element = dataSource.modelAtIndex(indexPath.item) else { - throw RxCocoaError.ItemsNotYetBound(object: self) - } - - return element + let element = try dataSource.modelAtIndexPath(indexPath) + + return element as! T } } #endif diff --git a/RxCocoa/iOS/UITableView+Rx.swift b/RxCocoa/iOS/UITableView+Rx.swift index c723198a..678bda59 100644 --- a/RxCocoa/iOS/UITableView+Rx.swift +++ b/RxCocoa/iOS/UITableView+Rx.swift @@ -193,22 +193,13 @@ extension UITableView { /** Reactive wrapper for `delegate` message `tableView:didSelectRowAtIndexPath:`. - It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence. + It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence, + or any other data source conforming to `SectionedViewDataSourceType` protocol. + ``` tableView.rx_modelSelected(MyModel.self) .map { ... - - If custom data source is being bound, new `rx_modelSelected` wrapper needs to be written also. - - public func rx_myModelSelected() -> ControlEvent { - let source: Observable = rx_itemSelected.map { indexPath in - let dataSource: MyDataSource = self.rx_dataSource.forwardToDelegate() as! MyDataSource - - return dataSource.modelAtIndex(indexPath.item)! - } - - return ControlEvent(source: source) - } + ``` */ public func rx_modelSelected(modelType: T.Type) -> ControlEvent { let source: Observable = rx_itemSelected.flatMap { [weak self] indexPath -> Observable in @@ -225,22 +216,13 @@ extension UITableView { /** Reactive wrapper for `delegate` message `tableView:didDeselectRowAtIndexPath:`. - It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence. + It can be only used when one of the `rx_itemsWith*` methods is used to bind observable sequence, + or any other data source conforming to `SectionedViewDataSourceType` protocol. - tableView.rx_modelSelected(MyModel.self) - .map { ... - - If custom data source is being bound, new `rx_modelSelected` wrapper needs to be written also. - - public func rx_myModelDeselected() -> ControlEvent { - let source: Observable = rx_itemDeselected.map { indexPath in - let dataSource: MyDataSource = self.rx_dataSource.forwardToDelegate() as! MyDataSource - - return dataSource.modelAtIndex(indexPath.item)! - } - - return ControlEvent(source: source) - } + ``` + tableView.rx_modelDeselected(MyModel.self) + .map { ... + ``` */ public func rx_modelDeselected(modelType: T.Type) -> ControlEvent { let source: Observable = rx_itemDeselected.flatMap { [weak self] indexPath -> Observable in @@ -255,16 +237,14 @@ extension UITableView { } /** - Synchronous helper method for retrieving a model at indexPath through a reactive data source + Synchronous helper method for retrieving a model at indexPath through a reactive data source. */ public func rx_modelAtIndexPath(indexPath: NSIndexPath) throws -> T { - let dataSource: RxTableViewReactiveArrayDataSource = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_items*` methods was used.") + let dataSource: SectionedViewDataSourceType = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_items*` methods was used.") - guard let element = dataSource.modelAtIndex(indexPath.item) else { - throw RxCocoaError.ItemsNotYetBound(object: self) - } - - return element + let element = try dataSource.modelAtIndexPath(indexPath) + + return element as! T } } diff --git a/RxExample/RxExample.xcodeproj/xcshareddata/xcschemes/RxExample-OSX.xcscheme b/RxExample/RxExample.xcodeproj/xcshareddata/xcschemes/RxExample-OSX.xcscheme index 237d82f8..95419b62 100644 --- a/RxExample/RxExample.xcodeproj/xcshareddata/xcschemes/RxExample-OSX.xcscheme +++ b/RxExample/RxExample.xcodeproj/xcshareddata/xcschemes/RxExample-OSX.xcscheme @@ -1,6 +1,6 @@ = Observable.just([1, 2, 3]) let layout = UICollectionViewFlowLayout() @@ -137,7 +137,7 @@ extension ControlTests { s.dispose() } - func testCollectionView_ModelSelected2() { + func testCollectionView_ModelSelected_itemsWithCellIdentifier() { let items: Observable<[Int]> = Observable.just([1, 2, 3]) let layout = UICollectionViewFlowLayout() @@ -167,6 +167,90 @@ extension ControlTests { s.dispose() dataSourceSubscription.dispose() } + + func testCollectionView_ModelDeselected_itemsWithCellFactory() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let layout = UICollectionViewFlowLayout() + + let createView: () -> (UICollectionView, Disposable) = { + let collectionView = UICollectionView(frame: CGRectMake(0, 0, 1, 1), collectionViewLayout: layout) + let s = items.bindTo(collectionView.rx_itemsWithCellFactory) { (cv, index: Int, item: Int) -> UICollectionViewCell in + return UICollectionViewCell(frame: CGRectMake(1, 1, 1, 1)) + } + + return (collectionView, s) + } + + let (collectionView, dataSourceSubscription) = createView() + + var selectedItem: Int? = nil + + let s = collectionView.rx_modelDeselected(Int.self) + .subscribeNext { (item: Int) in + selectedItem = item + } + + collectionView.delegate!.collectionView!(collectionView, didDeselectItemAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) + + XCTAssertEqual(selectedItem, 2) + + dataSourceSubscription.dispose() + s.dispose() + } + + func testCollectionView_ModelDeselected_itemsWithCellIdentifier() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let layout = UICollectionViewFlowLayout() + let createView: () -> (UICollectionView, Disposable) = { + let collectionView = UICollectionView(frame: CGRectMake(0, 0, 1, 1), collectionViewLayout: layout) + collectionView.registerClass(NSClassFromString("UICollectionViewCell"), forCellWithReuseIdentifier: "a") + let dataSourceSubscription = items.bindTo(collectionView.rx_itemsWithCellIdentifier("a")) { (index: Int, item: Int, cell) in + + } + + return (collectionView, dataSourceSubscription) + + } + let (collectionView, dataSourceSubscription) = createView() + + var selectedItem: Int? = nil + + let s = collectionView.rx_modelDeselected(Int.self) + .subscribeNext { item in + selectedItem = item + } + + collectionView.delegate!.collectionView!(collectionView, didDeselectItemAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) + + XCTAssertEqual(selectedItem, 2) + + s.dispose() + dataSourceSubscription.dispose() + } + + func testCollectionView_modelAtIndexPath_normal() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let layout = UICollectionViewFlowLayout() + let createView: () -> (UICollectionView, Disposable) = { + let collectionView = UICollectionView(frame: CGRectMake(0, 0, 1, 1), collectionViewLayout: layout) + collectionView.registerClass(NSClassFromString("UICollectionViewCell"), forCellWithReuseIdentifier: "a") + let dataSource = SectionedViewDataSourceMock() + let dataSourceSubscription = items.bindTo(collectionView.rx_itemsWithDataSource(dataSource)) + + return (collectionView, dataSourceSubscription) + + } + let (collectionView, dataSourceSubscription) = createView() + + let model: Int = try! collectionView.rx_modelAtIndexPath(NSIndexPath(forItem: 1, inSection: 0)) + + XCTAssertEqual(model, 2) + + dataSourceSubscription.dispose() + } } // UILabel @@ -246,7 +330,7 @@ extension ControlTests { ensureEventDeallocated(createView) { (view: UITableView) in view.rx_modelSelected(Int.self) } } - func testTableView_ModelSelected1() { + func testTableView_ModelSelected_rx_itemsWithCellFactory() { let items: Observable<[Int]> = Observable.just([1, 2, 3]) let createView: () -> (UITableView, Disposable) = { @@ -275,7 +359,7 @@ extension ControlTests { s.dispose() } - func testTableView_ModelSelected2() { + func testTableView_ModelSelected_itemsWithCellIdentifier() { let items: Observable<[Int]> = Observable.just([1, 2, 3]) let createView: () -> (UITableView, Disposable) = { @@ -304,6 +388,86 @@ extension ControlTests { dataSourceSubscription.dispose() s.dispose() } + + func testTableView_ModelDeselected_rx_itemsWithCellFactory() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let createView: () -> (UITableView, Disposable) = { + let tableView = UITableView(frame: CGRectMake(0, 0, 1, 1)) + let dataSourceSubscription = items.bindTo(tableView.rx_itemsWithCellFactory) { (tv, index: Int, item: Int) -> UITableViewCell in + return UITableViewCell(style: .Default, reuseIdentifier: "Identity") + } + + return (tableView, dataSourceSubscription) + } + + let (tableView, dataSourceSubscription) = createView() + + var selectedItem: Int? = nil + + let s = tableView.rx_modelDeselected(Int.self) + .subscribeNext { item in + selectedItem = item + } + + tableView.delegate!.tableView!(tableView, didDeselectRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) + + XCTAssertEqual(selectedItem, 2) + + dataSourceSubscription.dispose() + s.dispose() + } + + func testTableView_ModelDeselected_itemsWithCellIdentifier() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let createView: () -> (UITableView, Disposable) = { + let tableView = UITableView(frame: CGRectMake(0, 0, 1, 1)) + tableView.registerClass(NSClassFromString("UITableViewCell"), forCellReuseIdentifier: "a") + let dataSourceSubscription = items.bindTo(tableView.rx_itemsWithCellIdentifier("a")) { (index: Int, item: Int, cell) in + + } + + return (tableView, dataSourceSubscription) + } + + let (tableView, dataSourceSubscription) = createView() + + var selectedItem: Int? = nil + + let s = tableView.rx_modelDeselected(Int.self) + .subscribeNext { item in + selectedItem = item + } + + tableView.delegate!.tableView!(tableView, didDeselectRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) + + XCTAssertEqual(selectedItem, 2) + + dataSourceSubscription.dispose() + s.dispose() + } + + func testTableView_modelAtIndexPath_normal() { + let items: Observable<[Int]> = Observable.just([1, 2, 3]) + + let createView: () -> (UITableView, Disposable) = { + let tableView = UITableView(frame: CGRectMake(0, 0, 1, 1)) + tableView.registerClass(NSClassFromString("UITableViewCell"), forCellReuseIdentifier: "a") + let dataSource = SectionedViewDataSourceMock() + let dataSourceSubscription = items.bindTo(tableView.rx_itemsWithDataSource(dataSource)) + + return (tableView, dataSourceSubscription) + } + + let (tableView, dataSourceSubscription) = createView() + + let model: Int = try! tableView.rx_modelAtIndexPath(NSIndexPath(forItem: 1, inSection: 0)) + + XCTAssertEqual(model, 2) + + dataSourceSubscription.dispose() + } } // UIControl diff --git a/Tests/RxCocoaTests/TestImplementations/SectionedViewDataSourceMock.swift b/Tests/RxCocoaTests/TestImplementations/SectionedViewDataSourceMock.swift new file mode 100644 index 00000000..e991b071 --- /dev/null +++ b/Tests/RxCocoaTests/TestImplementations/SectionedViewDataSourceMock.swift @@ -0,0 +1,57 @@ +// +// SectionedViewDataSourceMock.swift +// Rx +// +// Created by Krunoslav Zaher on 1/10/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import RxSwift +import RxCocoa +import UIKit + +@objc class SectionedViewDataSourceMock + : NSObject + , SectionedViewDataSourceType + , UITableViewDataSource + , UICollectionViewDataSource + , RxTableViewDataSourceType + , RxCollectionViewDataSourceType { + + typealias Element = [Int] + + var items: [Int]? + + override init() { + super.init() + } + + func modelAtIndexPath(indexPath: NSIndexPath) throws -> Any { + return items![indexPath.item] + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 0 + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + return UITableViewCell() + } + + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 0 + } + + func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + return UICollectionViewCell() + } + + func tableView(tableView: UITableView, observedEvent: Event) { + items = observedEvent.element! + } + + func collectionView(collectionView: UICollectionView, observedEvent: Event) { + items = observedEvent.element! + } +} \ No newline at end of file