代碼下載下傳
UIPickerView的Rx實作分析
RxPickerViewDelegateProxy分析
RxCocoa
已經實作了
RxPickerViewDelegateProxy
,該類繼承
DelegateProxy
基類,遵守
DelegateProxyType
、
UIPickerViewDelegate
協定:
extension UIPickerView: HasDelegate {
public typealias Delegate = UIPickerViewDelegate
}
open class RxPickerViewDelegateProxy
: DelegateProxy<UIPickerView, UIPickerViewDelegate>
, DelegateProxyType
, UIPickerViewDelegate {
/// Typed parent object.
public weak private(set) var pickerView: UIPickerView?
/// - parameter pickerView: Parent object for delegate proxy.
public init(pickerView: ParentObject) {
self.pickerView = pickerView
super.init(parentObject: pickerView, delegateProxy: RxPickerViewDelegateProxy.self)
}
// Register known implementationss
public static func registerKnownImplementations() {
self.register { RxPickerViewDelegateProxy(pickerView: $0) }
}
}
UIPickerViewDelegate
協定的所有函數在這個類中都沒有實作,最終會進行消息轉發。
RxPickerViewDataSourceProxy分析
RxCocoa
已經實作了
RxPickerViewDataSourceProxy
,該類繼承
DelegateProxy
基類,遵守
DelegateProxyType
、
UIPickerViewDataSource
協定:
extension UIPickerView: HasDataSource {
public typealias DataSource = UIPickerViewDataSource
}
private let pickerViewDataSourceNotSet = PickerViewDataSourceNotSet()
final private class PickerViewDataSourceNotSet: NSObject, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 0
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 0
}
}
/// For more information take a look at `DelegateProxyType`.
public class RxPickerViewDataSourceProxy
: DelegateProxy<UIPickerView, UIPickerViewDataSource>
, DelegateProxyType
, UIPickerViewDataSource {
/// Typed parent object.
public weak private(set) var pickerView: UIPickerView?
/// - parameter pickerView: Parent object for delegate proxy.
public init(pickerView: ParentObject) {
self.pickerView = pickerView
super.init(parentObject: pickerView, delegateProxy: RxPickerViewDataSourceProxy.self)
}
// Register known implementations
public static func registerKnownImplementations() {
self.register { RxPickerViewDataSourceProxy(pickerView: $0) }
}
private weak var _requiredMethodsDataSource: UIPickerViewDataSource? = pickerViewDataSourceNotSet
// MARK: UIPickerViewDataSource
/// Required delegate method implementation.
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).numberOfComponents(in: pickerView)
}
/// Required delegate method implementation.
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).pickerView(pickerView, numberOfRowsInComponent: component)
}
/// For more information take a look at `DelegateProxyType`.
public override func setForwardToDelegate(_ forwardToDelegate: UIPickerViewDataSource?, retainDelegate: Bool) {
_requiredMethodsDataSource = forwardToDelegate ?? pickerViewDataSourceNotSet
super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate)
}
}
擴充UIPickerView遵守HasDataSource協定,使
RxPickerViewDataSourceProxy
滿足條件進而實作了
DelegateProxyType
協定中定義的如下兩個函數:
extension DelegateProxyType where ParentObject: HasDataSource, Self.Delegate == ParentObject.DataSource {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
return object.dataSource
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.dataSource = delegate
}
}
定義
PickerViewDataSourceNotSet
這個類為
UIPickerViewDataSource
協定提供預設資料
重寫
setForwardToDelegate
函數,将參數
forwardToDelegate
存儲在私有屬性
_requiredMethodsDataSource
中。
UIPickerViewDataSource
協定中的兩個函數在這個類中實作,調用屬性
_requiredMethodsDataSource
的方法傳回結果。
RxPickerViewAdapter分析
RxPickerViewAdapter
是用來處理
UIPickerViewDataSource
、
UIPickerViewDelegate
協定中定義的帶有傳回值的函數的。
RxCocoa
實作了3種
RxPickerViewAdapter
,分别是
RxStringPickerViewAdapter
、
RxAttributedStringPickerViewAdapter
、
RxPickerViewAdapter
。
RxPickerViewArrayDataSource分析
RxPickerViewArrayDataSource
是
RxStringPickerViewAdapter
、
RxAttributedStringPickerViewAdapter
、
RxPickerViewAdapter
的根類,遵守
UIPickerViewDataSource
、
SectionedViewDataSourceType
協定,還定義一個泛型
<T>
作為通用資料類型:
class RxPickerViewArrayDataSource<T>: NSObject, UIPickerViewDataSource, SectionedViewDataSourceType {
fileprivate var items: [T] = []
func model(at indexPath: IndexPath) throws -> Any {
guard items.indices ~= indexPath.row else {
throw RxCocoaError.itemsNotYetBound(object: self)
}
return items[indexPath.row]
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return items.count
}
}
-
數組屬性存儲所需資料,問題是他隻能存一組資料。items
- 實作
協定。UIPickerViewDataSource
RxPickerViewSequenceDataSource分析
RxPickerViewSequenceDataSource
這個類繼承自
RxPickerViewArrayDataSource
,是
RxStringPickerViewAdapter
、
RxAttributedStringPickerViewAdapter
、
RxPickerViewAdapter
的父類,遵守
RxPickerViewArrayDataSource<Sequence.Element>
,
RxPickerViewDataSourceType
協定,定義一個遵守
Swift.Sequence
協定的泛型
<Sequence>
:
class RxPickerViewSequenceDataSource<Sequence: Swift.Sequence>
: RxPickerViewArrayDataSource<Sequence.Element>
, RxPickerViewDataSourceType {
typealias Element = Sequence
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Sequence>) {
Binder(self) { dataSource, items in
dataSource.items = items
pickerView.reloadAllComponents()
}
.on(observedEvent.map(Array.init))
}
}
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Sequence>)
函數的實作:
- 建構一個
并執行Binder
操作,實際相當于直接執行建構on
的Binder
閉包。binding
- 将
類型參數Event<Sequence>
轉化為observedEvent
作為Event<Array>
操作的參數。on
- 在建構
的Binder
閉包中,将數組指派給binding
屬性并重新整理整個items
。UIPickerView
RxStringPickerViewAdapter、RxAttributedStringPickerViewAdapter、RxPickerViewAdapter分析
RxStringPickerViewAdapter
是不可被繼承的,自身繼承自
RxPickerViewSequenceDataSource
并且遵守
UIPickerViewDelegate
協定:
final class RxStringPickerViewAdapter<Sequence: Swift.Sequence>
: RxPickerViewSequenceDataSource<Sequence>
, UIPickerViewDelegate {
typealias TitleForRow = (Int, Sequence.Element) -> String?
private let titleForRow: TitleForRow
init(titleForRow: @escaping TitleForRow) {
self.titleForRow = titleForRow
super.init()
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return titleForRow(row, items[row])
}
}
屬性
titleForRow
存儲将
items
數組中的元素轉化為
String
類型的閉包。
- (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
函數中執行
titleForRow
閉包并傳回其結果。
RxAttributedStringPickerViewAdapter
、
RxPickerViewAdapter
的實作與
RxStringPickerViewAdapter
類似,不同的是分别存儲
attributedTitleForRow
(将數組中元素轉化為
NSAttributedString
富文本)、
viewForRow
(将數組中元素轉化為
UIView
視圖)的閉包,實作不同的
UIPickerViewDelegate
協定函數傳回執行閉包的傳回值:
final class RxAttributedStringPickerViewAdapter<Sequence: Swift.Sequence>: RxPickerViewSequenceDataSource<Sequence>, UIPickerViewDelegate {
typealias AttributedTitleForRow = (Int, Sequence.Element) -> NSAttributedString?
private let attributedTitleForRow: AttributedTitleForRow
init(attributedTitleForRow: @escaping AttributedTitleForRow) {
self.attributedTitleForRow = attributedTitleForRow
super.init()
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
return attributedTitleForRow(row, items[row])
}
}
final class RxPickerViewAdapter<Sequence: Swift.Sequence>: RxPickerViewSequenceDataSource<Sequence>, UIPickerViewDelegate {
typealias ViewForRow = (Int, Sequence.Element, UIView?) -> UIView
private let viewForRow: ViewForRow
init(viewForRow: @escaping ViewForRow) {
self.viewForRow = viewForRow
super.init()
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
return viewForRow(row, items[row], view)
}
}
基于UIPickerView的Reactive擴充分析
定義delegate、dataSource兩個函數用于擷取Rx代理對象,友善使用:
public var delegate: DelegateProxy<UIPickerView, UIPickerViewDelegate> {
return RxPickerViewDelegateProxy.proxy(for: base)
}
public var dataSource: DelegateProxy<UIPickerView, UIPickerViewDataSource> {
return RxPickerViewDataSourceProxy.proxy(for: base)
}
定義setDelegate函數通過
installForwardDelegate
函數為Rx代理對象設定
forwardDelegate
并傳回一個置空
forwardDelegate
的
Disposables
:
public func setDelegate(_ delegate: UIPickerViewDelegate)
-> Disposable {
return RxPickerViewDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base)
}
public func items<Source: ObservableType, Adapter: RxPickerViewDataSourceType & UIPickerViewDataSource & UIPickerViewDelegate>(adapter: Adapter) -> (_ source: Source) -> Disposable where Source.Element == Adapter.Element
函數,這個函數很重要,資料的綁定最終都會走到這個函數:
public func items<Source: ObservableType,
Adapter: RxPickerViewDataSourceType & UIPickerViewDataSource & UIPickerViewDelegate>(adapter: Adapter)
-> (_ source: Source)
-> Disposable where Source.Element == Adapter.Element {
return { source in
let delegateSubscription = self.setDelegate(adapter)
let dataSourceSubscription = source.subscribeProxyDataSource(ofObject: self.base, dataSource: adapter, retainDataSource: true, binding: { [weak pickerView = self.base] (_: RxPickerViewDataSourceProxy, event) in
guard let pickerView = pickerView else { return }
adapter.pickerView(pickerView, observedEvent: event)
})
return Disposables.create(delegateSubscription, dataSourceSubscription)
}
}
- 此函數接收一個遵守
、RxPickerViewDataSourceType
、UIPickerViewDataSource
這三個協定的參數UIPickerViewDelegate
,傳回一個adapter
綁定資料的閉包(_ source: Source) -> Disposable
- 建構傳回閉包,将原函數參數
通過adapter
函數設定給setDelegate
代理對象的RxDelegate
。forwardDelegate
- 将原函數參數
通過原函數參數adapter
的source
(見下方分析)函數設定給subscribeProxyDataSource
代理對象的RxDataSource
,并在forwardDelegate
參數的閉包中執行binding
的adapter
函數綁定資料。func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Element>)
- 最後将
和setDelegate
函數傳回的subscribeProxyDataSource
組合成一個Disposables
傳回。Disposables
itemTitles
,
itemAttributedTitles
,
items
這三個函數分别是将一個
Observable
中的資料綁定到
UIPickerView
的标題、富文本标題、以及view上:
public func itemTitles<Sequence: Swift.Sequence, Source: ObservableType>
(_ source: Source)
-> (_ titleForRow: @escaping (Int, Sequence.Element) -> String?)
-> Disposable where Source.Element == Sequence {
return { titleForRow in
let adapter = RxStringPickerViewAdapter<Sequence>(titleForRow: titleForRow)
return self.items(adapter: adapter)(source)
}
}
public func itemAttributedTitles<Sequence: Swift.Sequence, Source: ObservableType>
(_ source: Source)
-> (_ attributedTitleForRow: @escaping (Int, Sequence.Element) -> NSAttributedString?)
-> Disposable where Source.Element == Sequence {
return { attributedTitleForRow in
let adapter = RxAttributedStringPickerViewAdapter<Sequence>(attributedTitleForRow: attributedTitleForRow)
return self.items(adapter: adapter)(source)
}
}
public func items<Sequence: Swift.Sequence, Source: ObservableType>
(_ source: Source)
-> (_ viewForRow: @escaping (Int, Sequence.Element, UIView?) -> UIView)
-> Disposable where Source.Element == Sequence {
return { viewForRow in
let adapter = RxPickerViewAdapter<Sequence>(viewForRow: viewForRow)
return self.items(adapter: adapter)(source)
}
}
- 這三個函數的形式都是一樣的,接收一個
序列的資料參數,傳回一個閉包。Observable
- 不同的是傳回的閉包的參數不一樣,分别是将原函數
序列的元素轉化為Observable
、String
、NSAttributedString
的閉包。UIView
- 原函數都是直接建構一個閉包傳回,在閉包中将轉換原函數
序列的元素的閉包參數分别建構為Observable
、RxStringPickerViewAdapter
、RxAttributedStringPickerViewAdapter
對象,然後調用前面分析的RxPickerViewAdapter
函數通過各自的public func items<Source: ObservableType, Adapter: RxPickerViewDataSourceType & UIPickerViewDataSource & UIPickerViewDelegate>(adapter: Adapter) -> (_ source: Source) -> Disposable where Source.Element == Adapter.Element
将将原函數adapter
序列的元素綁定到Observable
并傳回UIPickerView
。Disposable
public func model<T>(at indexPath: IndexPath) throws -> T
函數用于擷取每個單元格中的資料對象:
public func model<T>(at indexPath: IndexPath) throws -> T {
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemTitles, rx.itemAttributedTitles, items(_ source: O)` methods was used.")
return castOrFatalError(try dataSource.model(at: indexPath))
}
- 取出
對象,實際上就是前面設定的self.dataSource.forwardToDelegate()
對象。adapter
- 由于
對象的基類adapter
實作了RxPickerViewArrayDataSource
協定中定義的SectionedViewDataSourceType
函數,是以傳回其調用結果。func model(at indexPath: IndexPath) throws -> Any
使用消息轉發的方式實作
itemSelected
序列,并使用
map
操作符更改序列元素。
modelSelected
序列是通過
itemSelected
序列進行轉化而來:
public var itemSelected: ControlEvent<(row: Int, component: Int)> {
let source = delegate
.methodInvoked(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)))
.map {
return (row: try castOrThrow(Int.self, $0[1]), component: try castOrThrow(Int.self, $0[2]))
}
return ControlEvent(events: source)
}
public func modelSelected<T>(_ modelType: T.Type) -> ControlEvent<[T]> {
let source = itemSelected.flatMap { [weak view = self.base as UIPickerView] _, component -> Observable<[T]> in
guard let view = view else {
return Observable.empty()
}
let model: [T] = try (0 ..< view.numberOfComponents).map { component in
let row = view.selectedRow(inComponent: component)
return try view.rx.model(at: IndexPath(row: row, section: component))
}
return Observable.just(model)
}
return ControlEvent(events: source)
}
ObservableType擴充分析
func subscribeProxyDataSource<DelegateProxy: DelegateProxyType>(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event<Element>) -> Void) -> Disposable
函數的作用是為
Rx
代理對象設定
forwardDelegate
對象和訂閱自身時執行
binding
參數中的閉包相關操作:
extension ObservableType {
func subscribeProxyDataSource<DelegateProxy: DelegateProxyType>(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event<Element>) -> Void)
-> Disposable
where DelegateProxy.ParentObject: UIView
, DelegateProxy.Delegate: AnyObject {
let proxy = DelegateProxy.proxy(for: object)
let unregisterDelegate = DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
// this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75)
object.layoutIfNeeded()
let subscription = self.asObservable()
.observeOn(MainScheduler())
.catchError { error in
bindingError(error)
return Observable.empty()
}
// source can never end, otherwise it would release the subscriber, and deallocate the data source
.concat(Observable.never())
.takeUntil(object.rx.deallocated)
.subscribe { [weak object] (event: Event<Element>) in
if let object = object {
assert(proxy === DelegateProxy.currentDelegate(for: object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(String(describing: DelegateProxy.currentDelegate(for: object)))")
}
binding(proxy, event)
switch event {
case .error(let error):
bindingError(error)
unregisterDelegate.dispose()
case .completed:
unregisterDelegate.dispose()
default:
break
}
}
return Disposables.create { [weak object] in
subscription.dispose()
object?.layoutIfNeeded()
unregisterDelegate.dispose()
}
}
}
- 這個函數接收三個參數分别是Rx代理對象的持有者
、Rx代理類關聯類型object
的Delegate
、是否持有dataSource
的dataSource
、資料綁定閉包retainDataSource
。binding
- 首先調用Rx代理類的
函數獲得Rx代理對象,接着調用proxy
函數設定Rx代理對象的installForwardDelegate
。forwardDelegate
- 然後本
序列執行Observable
操作進行訂閱并在其參數subscribe
閉包中執行on
閉包。binding
- 最後将自身訂閱和
函數放回的installForwardDelegate
組合成一個傳回。Disposable
UIPickerView示例
簡單的綁定
建構3個UIPickerView分别為pickerView1、pickerView2、pickerView3……
然後建構資料并分别綁定到上面的3個UIPickerView:
// 綁定pickerView1
Observable.just([1, 2, 3, 4, 5, 6, 7, 8, 9])
.bind(to: pickerView1.rx.itemTitles, curriedArgument: { "\($1)" })
.disposed(by: bag)
pickerView1.rx.itemSelected
.subscribe(onNext: { print("選中了第\($1)列第\($0)行") })
.disposed(by: bag)
pickerView1.rx.modelSelected(Int.self)
.subscribe(onNext: { print("選中了元素\($0.first ?? 0)") })
.disposed(by: bag)
// 綁定pickerView2
Observable.just([1, 2, 3, 4, 5, 6, 7, 8, 9])
.bind(to: pickerView2.rx.itemAttributedTitles, curriedArgument: { NSAttributedString(string: "\($1)", attributes: [NSAttributedString.Key.foregroundColor: UIColor.random]) })
.disposed(by: bag)
pickerView2.rx.itemSelected
.subscribe(onNext: { print("選中了第\($1)列第\($0)行") })
.disposed(by: bag)
pickerView2.rx.modelSelected(Int.self)
.subscribe(onNext: { print("選中了元素\($0.first ?? 0)") })
.disposed(by: bag)
// 綁定pickerView3
Observable.just(Array(0..<10).map({ (_) -> UIColor in UIColor.random }))
.bind(to: pickerView3.rx.items, curriedArgument: { _, color, _ in
let view = UIView()
view.backgroundColor = color
return view
}).disposed(by: bag)
pickerView3.rx.itemSelected
.subscribe(onNext: { print("選中了第\($1)列第\($0)行") })
.disposed(by: bag)
pickerView3.rx.modelSelected(UIColor.self)
.subscribe(onNext: { print("選中了元素\($0.first ?? UIColor.white)") })
.disposed(by: bag)
資料綁定過程分析
Observable.just([1, 2, 3, 4, 5, 6, 7, 8, 9])
.bind(to: pickerView1.rx.itemTitles, curriedArgument: { "\($1)" })
.disposed(by: bag)
RxCocoa對
func bind<R1, R2>(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2
函數的實作:
public func bind<R1, R2>(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2 {
return binder(self)(curriedArgument)
}
泛型
R1
為
(Int, Sequence.Element) -> String?
類型閉包,泛型
R2
為
Disposable
類型。于是上面資料綁定的代碼等價于:
pickerView1.rx
.itemTitles(Observable.just([1, 2, 3, 4, 5, 6, 7, 8, 9]))({ "\($1)" })
.disposed(by: bag)
資料綁定的完整過程:
- 建構元素是一個
類型的原資料[int]
序列。Observable
- 把原資料
序列作為參數執行Observable
函數,傳回一個pickerView1.rx.itemTitles
類型的閉包。(titleForRow: (Int, Sequence.Element) -> String?) -> Disposable
- 将類型為
的參數(Int, Sequence.Element) -> String?
這個閉包再作為curriedArgument
參數執行上面的titleForRow
閉包。(titleForRow: (Int, Sequence.Element) -> String?) -> Disposable
-
閉包内部使用(titleForRow: (Int, Sequence.Element) -> String?) -> Disposable
閉包建構titleForRow
類型對象RxStringPickerViewAdapter
。adapter
- 然後用
作為參數執行adapter
函數傳回類型為func items(adapter: Adapter) -> (source: Source) -> Disposable
的閉包。(source: Source) -> Disposable
- 将原資料
序列作為參數執行Observable
閉包。(source: Source) -> Disposable
- 閉包
内部執行(source: Source) -> Disposable
函數設定setDelegate
類型的RxPickerViewDelegateProxy
屬性。forwardDelegate
- 閉包
内部執行(source: Source) -> Disposable
函數設定subscribeProxyDataSource
類型的RxPickerViewDataSourceProxy
屬性。forwardDelegate
-
函數内部訂閱原資料subscribeProxyDataSource
序列将Observable
類型的元素作為參數執行[int]
對象的adapter
函數。func pickerView(pickerView: UIPickerView, observedEvent: Event<Element>)
- 最後
函數中将func pickerView(pickerView: UIPickerView, observedEvent: Event<Element>)
類型的元素儲存到[int]
對象的adapter
屬性中并執行items
重新整理reloadAllComponents
。UIPickerView
-
在重新整理時會調用其代理對象(UIPickerView
、RxPickerViewDelegateProxy
)的函數,然後在實作的協定函數中執行其RxPickerViewDataSourceProxy
屬性相應的函數傳回資料或者通過消息轉發的方式執行forwardDelegate
中的函數。forwardDelegate
pickerView1.rx.itemSelected
.subscribe(onNext: { print("選中了第\($1)列第\($0)行") })
.disposed(by: bag)
pickerView1.rx.modelSelected(Int.self)
.subscribe(onNext: { print("選中了元素\($0.first ?? 0)") })
.disposed(by: bag)
-
通過消息轉發實作。itemSelected
-
将modelSelected
通過itemSelected
操作符轉化flatMap
序列的行列号元素為實際資料。Observable
pickerView1
、
pickerView2
、
pickerView3
的資料綁定過程是一樣的。差別無非是将資料分别綁定到單元格的
title
、
attributedTitle
、
view
。
自定義綁定
從對
RxPickerViewArrayDataSource
的分析可以看出
RxCocoa
隻實作了将單組資料綁定到
UIPickerView
單元格的
title
、
attributedTitle
、
view
。
這往往滿足不了日常的開發需要,是以自定義綁定顯得很重要。
實作多組資料綁定
參照
RxCocoa
實作的
RxStringPickerViewAdapter
、
RxAttributedStringPickerViewAdapter
、
RxPickerViewAdapter
實作一個支援多組資料的基類
BaseSectionedPickerViewAdapter
:
class BaseSectionedPickerViewAdapter: NSObject, UIPickerViewDataSource, UIPickerViewDelegate, RxPickerViewDataSourceType, SectionedViewDataSourceType {
typealias Element = [[CustomStringConvertible]]
fileprivate var items: Element = []
func model(at indexPath: IndexPath) throws -> Any {
items[indexPath.section][indexPath.row]
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
items.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
items[component].count
}
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<[[CustomStringConvertible]]>) {
Binder(self) { (adapter, items) in
adapter.items = items
pickerView.reloadAllComponents()
}.on(observedEvent)
}
}
實作一個簡單的
SimpleSectionedPickerViewAdapter
,實作
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
函數将資料的
description
屬性簡單地綁定到
title
上:
class SimpleSectionedPickerViewAdapter: BaseSectionedPickerViewAdapter {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return items[component][row].description
}
}
在頁面上搭建一個
UIPickerView
命名為
pickerView
,然後進行資料綁定:
Observable.just([Array(0..<10), Array(10..<20), Array(20..<30)])
.bind(to: pickerView.rx.items(adapter: SimpleSectionedPickerViewAdapter()))
.disposed(by: bag)
RxCocoa
對
func bind<Result>(to binder: (Self) -> Result) -> Result
函數的實作:
public func bind<Result>(to binder: (Self) -> Result) -> Result {
return binder(self)
}
泛型
Result
為
Disposable
類型。于是上面資料綁定的代碼等價于:
pickerView.rx
.items(adapter: SimpleSectionedPickerViewAdapter())(Observable.just([Array(0..<10), Array(10..<20), Array(20..<30)]))
.disposed(by: bag)
資料綁定的完整過程:
- 首先建構一個
序列的原資料Observable
Observable.just([Array(0..<10), Array(10..<20), Array(20..<30)])
- 建構一個
類型對象作為參數執行PickerViewViewAdapter
函數傳回類型為func items(adapter: Adapter) -> (_ source: Source) -> Disposable
的閉包。(source: Source) -> Disposable
- 将原資料
序列作為參數執行Observable
閉包。(source: Source) -> Disposable
- 接下來的過程與上面的簡單綁定過程完全一緻。
就這樣實作了多組資料的簡單綁定,但是實際開發中往往需要控制
UIPickerView
單元格的寬、高、标題、視圖等内容,接下來實作更全面的資料綁定。
實作完整的資料綁定
實作一個
SectionedPickerViewAdapter
,與Rxcocoa實作的
RxPickerViewAdapter
類似:
class
<T: CustomStringConvertible>: BaseSectionedPickerViewAdapter<T>, UIPickerViewDelegate {
private var viewForRow: ((UIPickerView, Int, Int, UIView?) -> UIView)?
private var titleForRow: ((UIPickerView, Int, Int) -> String)?
private var attributedTitleForRow: ((UIPickerView, Int, Int) -> NSAttributedString)?
private var widthForComponent: ((UIPickerView, Int) -> CGFloat)?
private var heightForComponent: ((UIPickerView, Int) -> CGFloat)?
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
if let aView = viewForRow {
return aView(pickerView, row, component, view)
} else {
let label = UILabel()
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 21.0)
if let aAttributedTitle = attributedTitleForRow {
label.attributedText = aAttributedTitle(pickerView, row, component)
}
else if let aTitle = titleForRow {
label.text = aTitle(pickerView, row, component)
}
else {
label.text = items[component][row].description
}
return label
}
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if let width = widthForComponent {
return width(pickerView, component)
}
return floor(UIScreen.main.bounds.width/CGFloat(items.count))
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
if let aHeight = heightForComponent {
return aHeight(pickerView, component)
}
return 34.0
}
init(viewForRow: ((UIPickerView, Int, Int, UIView?) -> UIView)?, widthForComponent: ((UIPickerView, Int) -> CGFloat)? = nil, heightForComponent: ((UIPickerView, Int) -> CGFloat)? = nil, titleForRow: ((UIPickerView, Int, Int) -> String)? = nil, attributedTitleForRow: ((UIPickerView, Int, Int) -> NSAttributedString)? = nil) {
super.init()
self.viewForRow = viewForRow
self.widthForComponent = widthForComponent
self.heightForComponent = heightForComponent
}
}
擴充
Base
類型為
UIPickerView
的
Reactive
實作一個
sectionedItems
函數,與
RxCocoa
的
func items(_ source: Source) -> (_ viewForRow: @escaping (Int, Sequence.Element, UIView?) -> UIView) -> Disposable
函數類似:
extension Reactive where Base: UIPickerView {
func sectionedItems<T: CustomStringConvertible>
(_ source: Observable<[[T]]>)
-> ((viewForRow: (UIPickerView, Int, Int, UIView?) -> UIView, widthForComponent: ((UIPickerView, Int) -> CGFloat)?, heightForComponent: ((UIPickerView, Int) -> CGFloat)?, titleForRow:((UIPickerView, Int, Int) -> String)?, attributedTitleForRow: ((UIPickerView, Int, Int) -> NSAttributedString)?))
-> Disposable {
return { arg in
let adapter = SectionedPickerViewAdapter<T>(viewForRow: arg.0, widthForComponent: arg.1, heightForComponent: arg.2, titleForRow: arg.3, attributedTitleForRow: arg.4)
return items(adapter: adapter)(source)
}
}
}
最後進行資料綁定,其綁定過程與前邊分析基本一緻:
Observable.just([Array(0..<10), Array(10..<100), Array(100..<1000)])
.bind(to: centerPickerView.rx.sectionedItems, curriedArgument: ({ (_, row, component, _) in
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12.0)
label.textAlignment = .center
label.backgroundColor = UIColor.random
label.text = data[component][row].description
return label
}, { _, component in
switch component {
case 0:
return 40.0
case 1:
return 80.0
default:
return 120.0
}
}, { (_, _) in 50.0 }, nil, nil))
.disposed(by: bag)
通過前面對RxCocoa實作UIPickerView綁定的分析,也可以通過如下方式綁定資料:
let adapter = SectionedPickerViewAdapter<Int>(viewForRow: { (_, row, component, _) -> UIView in
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12.0)
label.textAlignment = .center
label.backgroundColor = UIColor.random
label.text = data[component][row].description
return label
}, widthForComponent: { _, component in
switch component {
case 0:
return 120.0
case 1:
return 80.0
default:
return 40.0
}
}, heightForComponent: { (_, _) in 40.0 })
Observable.just([Array(0..<10), Array(10..<100), Array(100..<1000)])
.bind(to: bottomPickerView.rx.items(adapter: adapter))
.disposed(by: bag)