天天看点

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)