RxSwift官方實例七(UIPickerView)

代碼下載

UIPickerView的Rx實現分析

RxPickerViewDelegateProxy分析

RxCocoa已經實現了RxPickerViewDelegateProxy,該類繼承DelegateProxy基類,遵守DelegateProxyTypeUIPickerViewDelegate協議:

    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基類,遵守DelegateProxyTypeUIPickerViewDataSource協議:

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是用來處理UIPickerViewDataSourceUIPickerViewDelegate協議中定義的帶有返回值的函數的。

RxCocoa實現了3種RxPickerViewAdapter,分別是RxStringPickerViewAdapterRxAttributedStringPickerViewAdapterRxPickerViewAdapter

RxPickerViewArrayDataSource分析

RxPickerViewArrayDataSourceRxStringPickerViewAdapterRxAttributedStringPickerViewAdapterRxPickerViewAdapter的根類,遵守UIPickerViewDataSourceSectionedViewDataSourceType協議,還定義一個泛型<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,是RxStringPickerViewAdapterRxAttributedStringPickerViewAdapterRxPickerViewAdapter的父類,遵守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操作,實際相當於直接執行構建Binderbinding閉包。
  • Event<Sequence>類型參數observedEvent轉化爲Event<Array>作爲on操作的參數。
  • 在構建Binderbinding閉包中,將數組賦值給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並返回一個置空forwardDelegateDisposables

        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)
                }
        }
  • 此函數接收一個遵守RxPickerViewDataSourceTypeUIPickerViewDataSourceUIPickerViewDelegate這三個協議的參數adapter,返回一個(_ source: Source) -> Disposable綁定數據的閉包
  • 構建返回閉包,將原函數參數adapter通過setDelegate函數設置給RxDelegate代理對象的forwardDelegate
  • 將原函數參數adapter通過原函數參數sourcesubscribeProxyDataSource(見下方分析)函數設置給RxDataSource代理對象的forwardDelegate,並在binding參數的閉包中執行adapterfunc pickerView(_ pickerView: UIPickerView, observedEvent: Event<Element>)函數綁定數據。
  • 最後將setDelegatesubscribeProxyDataSource函數返回的Disposables組合成一個Disposables返回。

itemTitlesitemAttributedTitlesitems這三個函數分別是將一個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序列的元素轉化爲StringNSAttributedStringUIView的閉包。
  • 原函數都是直接構建一個閉包返回,在閉包中將轉換原函數Observable序列的元素的閉包參數分別構建爲RxStringPickerViewAdapterRxAttributedStringPickerViewAdapterRxPickerViewAdapter對象,然後調用前面分析的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代理對象設置

        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代理類關聯類型DelegatedataSource、是否持有dataSourceretainDataSource、數據綁定閉包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?類型閉包,泛型R2Disposable類型。於是上面數據綁定的代碼等價於:

        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在刷新時會調用其代理對象(RxPickerViewDelegateProxyRxPickerViewDataSourceProxy)的函數,然後在實現的協議函數中執行其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通過消息轉發實現。
  • modelSelecteditemSelected通過flatMap操作符轉化Observable序列的行列號元素爲實際數據。

pickerView1pickerView2pickerView3的數據綁定過程是一樣的。區別無非是將數據分別綁定到單元格的titleattributedTitleview

自定義綁定

從對RxPickerViewArrayDataSource的分析可以看出RxCocoa只實現了將單組數據綁定到UIPickerView單元格的titleattributedTitleview

這往往滿足不了日常的開發需要,所以自定義綁定顯得很重要。

實現多組數據綁定

參照RxCocoa實現的RxStringPickerViewAdapterRxAttributedStringPickerViewAdapterRxPickerViewAdapter實現一個支持多組數據的基類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)

RxCocoafunc bind<Result>(to binder: (Self) -> Result) -> Result函數的實現:

    public func bind<Result>(to binder: (Self) -> Result) -> Result {
        return binder(self)
    }

泛型ResultDisposable類型。於是上面數據綁定的代碼等價於:

        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類型爲UIPickerViewReactive實現一個sectionedItems函數,與RxCocoafunc 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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章