Skip to content

Commit 7b866f0

Browse files
committed
Improves documentation to UIPickerView components, changes modelSelected type to [T], simplifies UIPickerView adapter example.
1 parent b5e1d84 commit 7b866f0

File tree

7 files changed

+77
-117
lines changed

7 files changed

+77
-117
lines changed

RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import RxSwift
1313
#endif
1414

15-
class RxPickerViewArrayDataSource<T>: NSObject, UIPickerViewDataSource, SectionedViewDataSourceType {
15+
private class RxPickerViewArrayDataSource<T>: NSObject, UIPickerViewDataSource, SectionedViewDataSourceType {
1616
fileprivate var items: [T] = []
1717

1818
func model(at indexPath: IndexPath) throws -> Any {

RxCocoa/iOS/Proxies/RxPickerViewDataSourceProxy.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
import RxSwift
1414
#endif
1515

16-
let pickerViewDataSourceNotSet = PickerViewDataSourceNotSet()
16+
fileprivate let pickerViewDataSourceNotSet = PickerViewDataSourceNotSet()
1717

18-
final class PickerViewDataSourceNotSet: NSObject, UIPickerViewDataSource {
18+
final fileprivate class PickerViewDataSourceNotSet: NSObject, UIPickerViewDataSource {
1919
func numberOfComponents(in pickerView: UIPickerView) -> Int {
2020
return 0
2121
}
@@ -25,25 +25,33 @@ final class PickerViewDataSourceNotSet: NSObject, UIPickerViewDataSource {
2525
}
2626
}
2727

28+
/// For more information take a look at `DelegateProxyType`.
2829
public class RxPickerViewDataSourceProxy
2930
: DelegateProxy
3031
, UIPickerViewDataSource
3132
, DelegateProxyType {
33+
34+
/// Typed parent object.
3235
public weak fileprivate(set) var pickerView: UIPickerView?
3336
private weak var _requiredMethodsDataSource: UIPickerViewDataSource? = pickerViewDataSourceNotSet
34-
37+
38+
/// Initializes `RxPickerViewDataSourceProxy`
39+
///
40+
/// - parameter parentObject: Parent object for delegate proxy.
3541
public required init(parentObject: AnyObject) {
3642
self.pickerView = castOrFatalError(parentObject)
3743
super.init(parentObject: parentObject)
3844
}
3945

4046

4147
// MARK: UIPickerViewDataSource
42-
48+
49+
/// Required delegate method implementation.
4350
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
4451
return (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).numberOfComponents(in: pickerView)
4552
}
46-
53+
54+
/// Required delegate method implementation.
4755
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
4856
return (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).pickerView(pickerView, numberOfRowsInComponent: component)
4957
}

RxCocoa/iOS/UIPickerView+Rx.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@
6565
/**
6666
Reactive wrapper for `delegate` message `pickerView:didSelectRow:inComponent:`.
6767
*/
68-
public var itemSelected: ControlEvent<(Int, Int)> {
68+
public var itemSelected: ControlEvent<(row: Int, component: Int)> {
6969
let source = delegate
7070
.methodInvoked(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)))
7171
.map {
72-
return (try castOrThrow(Int.self, $0[1]), try castOrThrow(Int.self, $0[2]))
72+
return (row: try castOrThrow(Int.self, $0[1]), component: try castOrThrow(Int.self, $0[2]))
7373
}
7474
return ControlEvent(events: source)
7575
}
@@ -86,12 +86,18 @@
8686
```
8787
- parameter modelType: Type of a Model which bound to the dataSource
8888
*/
89-
public func modelSelected<T>(_ modelType: T.Type) -> ControlEvent<T> {
90-
let source = itemSelected.flatMap { [weak view = self.base as UIPickerView] (row, component) -> Observable<T> in
89+
public func modelSelected<T>(_ modelType: T.Type) -> ControlEvent<[T]> {
90+
let source = itemSelected.flatMap { [weak view = self.base as UIPickerView] (_, component) -> Observable<[T]> in
9191
guard let view = view else {
9292
return Observable.empty()
9393
}
94-
return Observable.just(try view.rx.model(at: IndexPath(row: row, section: component)))
94+
95+
let model: [T] = try (0 ..< view.numberOfComponents).map { component in
96+
let row = view.selectedRow(inComponent: component)
97+
return try view.rx.model(at: IndexPath(row: row, section: component))
98+
}
99+
100+
return Observable.just(model)
95101
}
96102

97103
return ControlEvent(events: source)

RxExample/RxExample/Examples/UIPickerViewExample/CustomPickerViewAdapterExampleViewController.swift

Lines changed: 13 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,94 +13,35 @@ import UIKit
1313
#endif
1414

1515
final class CustomPickerViewAdapterExampleViewController: ViewController {
16-
@IBOutlet weak var pickerView1: UIPickerView!
17-
@IBOutlet weak var pickerView2: UIPickerView!
18-
@IBOutlet weak var pickerView3: UIPickerView!
19-
16+
@IBOutlet weak var pickerView: UIPickerView!
17+
2018
override func viewDidLoad() {
2119
super.viewDidLoad()
2220

2321
Observable.just([[1, 2, 3], [5, 8, 13], [21, 34]])
24-
.bind(to: pickerView1.rx.items(adapter: CustomStringPickerViewAdapter()))
22+
.bind(to: pickerView.rx.items(adapter: PickerViewViewAdapter()))
2523
.disposed(by: disposeBag)
26-
27-
Observable.just([[1, 2, 3], [5, 8, 13], [21, 34]])
28-
.bind(to: pickerView2.rx.items(adapter: CustomAttributedStringPickerViewAdapter()))
29-
.disposed(by: disposeBag)
30-
31-
Observable.just([[1, 2, 3], [5, 8, 13], [21, 34]])
32-
.bind(to: pickerView3.rx.items(adapter: PickerViewViewAdapter()))
24+
25+
pickerView.rx.modelSelected(Int.self)
26+
.subscribe(onNext: { models in
27+
print(models)
28+
})
3329
.disposed(by: disposeBag)
3430
}
3531
}
3632

37-
final class CustomStringPickerViewAdapter
33+
final class PickerViewViewAdapter
3834
: NSObject
3935
, UIPickerViewDataSource
4036
, UIPickerViewDelegate
41-
, RxPickerViewDataSourceType {
37+
, RxPickerViewDataSourceType
38+
, SectionedViewDataSourceType {
4239
typealias Element = [[CustomStringConvertible]]
4340
private var items: [[CustomStringConvertible]] = []
44-
45-
func numberOfComponents(in pickerView: UIPickerView) -> Int {
46-
return items.count
47-
}
48-
49-
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
50-
return items[component].count
51-
}
52-
53-
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
54-
return items[component][row].description
55-
}
56-
57-
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Element>) {
58-
UIBindingObserver(UIElement: self) { (adapter, items) in
59-
adapter.items = items
60-
pickerView.reloadAllComponents()
61-
}.on(observedEvent)
62-
}
63-
}
6441

65-
final class CustomAttributedStringPickerViewAdapter
66-
: NSObject
67-
, UIPickerViewDataSource
68-
, UIPickerViewDelegate
69-
, RxPickerViewDataSourceType {
70-
typealias Element = [[CustomStringConvertible]]
71-
private var items: [[CustomStringConvertible]] = []
72-
73-
func numberOfComponents(in pickerView: UIPickerView) -> Int {
74-
return items.count
75-
}
76-
77-
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
78-
return items[component].count
79-
}
80-
81-
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
82-
return NSAttributedString(string: items[component][row].description,
83-
attributes: [
84-
NSForegroundColorAttributeName: UIColor.cyan,
85-
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleDouble.rawValue
86-
])
87-
}
88-
89-
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Element>) {
90-
UIBindingObserver(UIElement: self) { (adapter, items) in
91-
adapter.items = items
92-
pickerView.reloadAllComponents()
93-
}.on(observedEvent)
42+
func model(at indexPath: IndexPath) throws -> Any {
43+
return items[indexPath.section][indexPath.row]
9444
}
95-
}
96-
97-
final class PickerViewViewAdapter
98-
: NSObject
99-
, UIPickerViewDataSource
100-
, UIPickerViewDelegate
101-
, RxPickerViewDataSourceType {
102-
typealias Element = [[CustomStringConvertible]]
103-
private var items: [[CustomStringConvertible]] = []
10445

10546
func numberOfComponents(in pickerView: UIPickerView) -> Int {
10647
return items.count

RxExample/RxExample/Examples/UIPickerViewExample/SimplePickerViewExampleViewController.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ final class SimplePickerViewExampleViewController: ViewController {
2626
return "\(item)"
2727
}
2828
.disposed(by: disposeBag)
29+
30+
pickerView1.rx.modelSelected(Int.self)
31+
.subscribe(onNext: { models in
32+
print("models selected 1: \(models)")
33+
})
34+
.disposed(by: disposeBag)
2935

3036
Observable.just([1, 2, 3])
3137
.bind(to: pickerView2.rx.itemAttributedTitles) { _, item in
@@ -36,6 +42,12 @@ final class SimplePickerViewExampleViewController: ViewController {
3642
])
3743
}
3844
.disposed(by: disposeBag)
45+
46+
pickerView2.rx.modelSelected(Int.self)
47+
.subscribe(onNext: { models in
48+
print("models selected 2: \(models)")
49+
})
50+
.disposed(by: disposeBag)
3951

4052
Observable.just([UIColor.red, UIColor.green, UIColor.blue])
4153
.bind(to: pickerView3.rx.items) { _, item, _ in
@@ -44,5 +56,11 @@ final class SimplePickerViewExampleViewController: ViewController {
4456
return view
4557
}
4658
.disposed(by: disposeBag)
59+
60+
pickerView3.rx.modelSelected(Int.self)
61+
.subscribe(onNext: { models in
62+
print("models selected 3: \(models)")
63+
})
64+
.disposed(by: disposeBag)
4765
}
4866
}

RxExample/RxExample/Examples/UIPickerViewExample/SimpleUIPickerViewExample.storyboard

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1510" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="h29-lJ-Sgs">
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="h29-lJ-Sgs">
33
<device id="retina4_7" orientation="portrait">
44
<adaptation id="fullscreen"/>
55
</device>
66
<dependencies>
77
<deployment identifier="iOS"/>
8-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
99
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
1010
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
1111
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -23,37 +23,21 @@
2323
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
2424
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2525
<subviews>
26-
<pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FUv-qb-kPm">
27-
<rect key="frame" x="0.0" y="0.0" width="375" height="222.5"/>
28-
</pickerView>
2926
<pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Qw-EC-A7z">
30-
<rect key="frame" x="0.0" y="395" width="375" height="222.5"/>
31-
</pickerView>
32-
<pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Jek-p5-MeP">
33-
<rect key="frame" x="0.0" y="222" width="375" height="222.5"/>
27+
<rect key="frame" x="0.0" y="20" width="375" height="597.5"/>
3428
</pickerView>
3529
</subviews>
3630
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
3731
<constraints>
38-
<constraint firstItem="Jek-p5-MeP" firstAttribute="leading" secondItem="8lm-KZ-WhU" secondAttribute="leadingMargin" constant="-16" id="0To-gV-vsk"/>
39-
<constraint firstItem="Jek-p5-MeP" firstAttribute="top" secondItem="FUv-qb-kPm" secondAttribute="bottom" constant="-0.5" id="2Ob-x4-V3b"/>
40-
<constraint firstItem="7Qw-EC-A7z" firstAttribute="height" secondItem="Jek-p5-MeP" secondAttribute="height" id="8ka-rT-fzR"/>
4132
<constraint firstItem="I6Z-oN-WS8" firstAttribute="top" secondItem="7Qw-EC-A7z" secondAttribute="bottom" constant="0.5" id="9BL-qX-gl5"/>
42-
<constraint firstItem="Jek-p5-MeP" firstAttribute="height" secondItem="FUv-qb-kPm" secondAttribute="height" id="S9j-bB-7dM"/>
43-
<constraint firstAttribute="trailingMargin" secondItem="FUv-qb-kPm" secondAttribute="trailing" constant="-16" id="SWq-eM-fu4"/>
33+
<constraint firstItem="7Qw-EC-A7z" firstAttribute="top" secondItem="61a-Hs-mqd" secondAttribute="bottom" id="Q5C-UX-P9i"/>
4434
<constraint firstItem="7Qw-EC-A7z" firstAttribute="leading" secondItem="8lm-KZ-WhU" secondAttribute="leadingMargin" constant="-16" id="Suo-sl-F3b"/>
45-
<constraint firstItem="FUv-qb-kPm" firstAttribute="top" secondItem="61a-Hs-mqd" secondAttribute="bottom" constant="-20" id="WcE-mF-beh"/>
46-
<constraint firstItem="FUv-qb-kPm" firstAttribute="height" secondItem="8lm-KZ-WhU" secondAttribute="height" multiplier="1:3" id="YBK-o9-J8J"/>
4735
<constraint firstAttribute="trailingMargin" secondItem="7Qw-EC-A7z" secondAttribute="trailing" constant="-16" id="ltJ-CE-JjD"/>
48-
<constraint firstItem="FUv-qb-kPm" firstAttribute="leading" secondItem="8lm-KZ-WhU" secondAttribute="leadingMargin" constant="-16" id="ncD-bk-j9H"/>
49-
<constraint firstAttribute="trailingMargin" secondItem="Jek-p5-MeP" secondAttribute="trailing" constant="-16" id="xxa-q4-NBW"/>
5036
</constraints>
5137
</view>
5238
<tabBarItem key="tabBarItem" title="Custom Adapters" id="F7B-2F-A59"/>
5339
<connections>
54-
<outlet property="pickerView1" destination="FUv-qb-kPm" id="tT9-eF-nRW"/>
55-
<outlet property="pickerView2" destination="Jek-p5-MeP" id="PGs-Oy-25Y"/>
56-
<outlet property="pickerView3" destination="7Qw-EC-A7z" id="hQX-Yf-u5o"/>
40+
<outlet property="pickerView" destination="7Qw-EC-A7z" id="Zd4-ee-qEP"/>
5741
</connections>
5842
</viewController>
5943
<placeholder placeholderIdentifier="IBFirstResponder" id="eYS-P5-6qD" userLabel="First Responder" sceneMemberID="firstResponder"/>

Tests/RxCocoaTests/UIPickerView+RxTests.swift

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ final class UIPickerViewTests: RxTest {
8686
let adapter = TestPickerViewAdapter()
8787

8888
outerPicker = pickerView
89+
_ = outerPicker
90+
8991
adapterSubscription = items.bind(to: pickerView.rx.items(adapter: adapter))
9092

9193
_ = adapter.rx.deallocated.subscribe(onNext: { _ in
9294
adapterDeallocated = true
9395
})
9496
}
95-
97+
9698
XCTAssertFalse(adapterDeallocated)
9799
autoreleasepool { adapterSubscription.dispose() }
98100
XCTAssertTrue(adapterDeallocated)
@@ -136,7 +138,7 @@ final class UIPickerViewTests: RxTest {
136138
}
137139

138140
func test_ModelSelected_itemTitles() {
139-
var selectedItem: Int!
141+
var selectedItem: [Int]!
140142
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
141143

142144
_ = Observable.just([1, 2, 3]).bind(to: pickerView.rx.itemTitles) { _, _ in
@@ -146,14 +148,14 @@ final class UIPickerViewTests: RxTest {
146148
.subscribe(onNext: { item in
147149
selectedItem = item
148150
})
151+
pickerView.selectRow(1, inComponent: 0, animated: false)
152+
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 1, inComponent: 0)
149153

150-
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 0, inComponent: 0)
151-
152-
XCTAssertEqual(selectedItem, 1)
154+
XCTAssertEqual(selectedItem, [2])
153155
}
154156

155157
func test_ModelSelected_itemAttributedTitles() {
156-
var selectedItem: Int!
158+
var selectedItem: [Int]!
157159
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
158160

159161
_ = Observable.just([1, 2, 3]).bind(to: pickerView.rx.itemAttributedTitles) { _, _ in
@@ -164,13 +166,14 @@ final class UIPickerViewTests: RxTest {
164166
selectedItem = item
165167
})
166168

167-
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 0, inComponent: 0)
169+
pickerView.selectRow(2, inComponent: 0, animated: false)
170+
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 2, inComponent: 0)
168171

169-
XCTAssertEqual(selectedItem, 1)
172+
XCTAssertEqual(selectedItem, [3])
170173
}
171174

172175
func test_ModelSelected_items() {
173-
var selectedItem: Int!
176+
var selectedItem: [Int]!
174177
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
175178

176179
_ = Observable.just([1, 2, 3])
@@ -184,11 +187,11 @@ final class UIPickerViewTests: RxTest {
184187

185188
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 0, inComponent: 0)
186189

187-
XCTAssertEqual(selectedItem, 1)
190+
XCTAssertEqual(selectedItem, [1])
188191
}
189192

190193
func test_ModelSelected_itemsWithCustomAdapter() {
191-
var selectedItem: Int!
194+
var selectedItem: [Int]!
192195
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
193196

194197
_ = Observable.just([[1, 2, 3]])
@@ -200,7 +203,7 @@ final class UIPickerViewTests: RxTest {
200203

201204
pickerView.delegate!.pickerView!(pickerView, didSelectRow: 0, inComponent: 0)
202205

203-
XCTAssertEqual(selectedItem, 1)
206+
XCTAssertEqual(selectedItem, [1])
204207
}
205208

206209
func test_modelAtIdexPath_ThrowsError_itemTitles() {

0 commit comments

Comments
 (0)