天天看點

RxSwift官方執行個體七(UIPickerView)

代碼下載下傳

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代理對象的持有者

    object

    、Rx代理類關聯類型

    Delegate

    dataSource

    、是否持有

    dataSource

    retainDataSource

    、資料綁定閉包

    binding

  • 首先調用Rx代理類的

    proxy

    函數獲得Rx代理對象,接着調用

    installForwardDelegate

    函數設定Rx代理對象的

    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)