天天看點

RxSwift官方執行個體六(UIImagePickerController)

代碼下載下傳

UIImagePickerController

搭建UI

建構如下UI:

RxSwift官方執行個體六(UIImagePickerController)

設定按鈕的是否可用:

cameraButton.isEnabled = UIImagePickerController.isSourceTypeAvailable(.camera)
           

UIImagePickerControllerDelegate的Rx實作

UIImagePickerController

的代理對象需要遵守

UIImagePickerControllerDelegate

UINavigationControllerDelegate

兩個協定的:

weak open var delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?
           

由于

RxCocoa

已經實作

UINavigationControllerDelegate

RxNavigationControllerDelegateProxy

,沒有實作

UIImagePickerControllerDelegate

,是以需要我們自己實作。

建構RxImagePickerDelegateProxy

從前面

Delegate

章節的

DelegateProxyType

講到,擁有

delegates

的視圖可能是繼承的,

RxCocoa

需要建立的那些代理也是繼承的。

由于

UIImagePickerController

繼承自

UINavigationController

,定義

RxImagePickerDelegateProxy

類繼承

RxNavigationControllerDelegateProxy

基類,遵守

UIImagePickerControllerDelegate

協定:

class RxImagePickerDelegateProxy: RxNavigationControllerDelegateProxy, UIImagePickerControllerDelegate {
    public init(imagePicker: UIImagePickerController) {
        super.init(navigationController: imagePicker)
    }
}
           

首先由于繼承自

RxNavigationControllerDelegateProxy

,是以實作了

DelegateProxyType

協定中定義的函數。

UIImagePickerControllerDelegate

協定中定義的函數都沒有實作,最終會走消息轉發的方式實作Rx序列發送元素。

擴充Reactive

首先定義一個

func dismissViewController(viewController: UIViewController, animated: Bool)

函數,用來安全有效地關閉模态視圖,友善後面使用:

func dismissViewController(viewController: UIViewController, animated: Bool) {
    /// 是否有控制器在程序中沒有顯示或消失
    if viewController.isBeingPresented || viewController.isBeingDismissed {
        DispatchQueue.main.async {// 異步遞歸調用
            dismissViewController(viewController: viewController, animated: animated)
        }
    } else if viewController.presentingViewController != nil {
        viewController.dismiss(animated: animated, completion: nil)
    }
}
           

定義一個

private func castOrThrow<T>(resultType: T.Type, object: Any) throws -> T

函數,用于将

Any

類型資料轉換為特定類型,友善後面使用:

private func castOrThrow<T>(resultType: T.Type, object: Any) throws -> T {
    guard let resultValue = object as? T else {
        throw RxCocoaError.castingError(object: object, targetType: resultType)
    }
    
    return resultValue
}
           

為了友善使用,基于

UIImagePickerController

擴充Reactive:

extension Reactive where Base: UIImagePickerController {
    public var didCancel: Observable<()> {
        return delegate.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerControllerDidCancel(_:))).map { (_) -> () in }
    }
    public var didFinishPickingMediaWithInfo: Observable<[UIImagePickerController.InfoKey: AnyObject]> {
        return delegate.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:))).map { (a) in
            return try castOrThrow(resultType: Dictionary<UIImagePickerController.InfoKey, AnyObject>.self, object: a[1])
        }
    }
    
    /// 建立圖檔選擇控制器Observable
    /// - Parameters:
    ///   - parent: 父控制器
    ///   - animated: 動畫
    ///   - configureImagePicker: 配置閉包
    static func createWithParent(parent: UIViewController?, animated: Bool = true, configureImagePicker: @escaping (UIImagePickerController) throws -> Void) -> Observable<UIImagePickerController> {
        return Observable.create { [weak parent] (observer) -> Disposable in
            let imagePicker = UIImagePickerController()
            // 取消操作
            let dismissDisposable = imagePicker.rx.didCancel.subscribe(onNext: { [weak imagePicker] (_) in
                guard let imagePicker = imagePicker else {
                    return
                }
                
                dismissViewController(viewController: imagePicker, animated: animated)
            })
            
            // 處理配置閉包
            do {
                try configureImagePicker(imagePicker)
            } catch let error {
                observer.onError(error)
                return Disposables.create()
            }
            
            guard let parent = parent else {
                observer.onCompleted()
                return Disposables.create()
            }
            parent.present(imagePicker, animated: animated, completion: nil)
            observer.on(.next(imagePicker))
            
            return Disposables.create(dismissDisposable, Disposables.create {
                dismissViewController(viewController: imagePicker, animated: animated)
            })
        }
    }
}
           

代碼分析:

  • 使用消息轉發的方式實作

    didCancel

    序列,并使用

    map

    操作符更改序列元素為空
  • 使用消息轉發的方式實作

    didFinishPickingMediaWithInfo

    序列,并使用

    map

    操作符更改序列元素
  • createWithParent

    序列的實作是使用

    create

    操作符建立,在建立的閉包中建立

    UIImagePickerController

    、執行配置閉包、

    present

    控制器、訂閱

    didCancel

    序列進行關閉控制器等

綁定UI

在iOS中使用相機、相冊資源,需要在

info.plist

檔案中配置相機、相冊權限詢問框的描述,如下所示:

RxSwift官方執行個體六(UIImagePickerController)

因為在使用

rx.*

之前(例如appDidFinishLaunching),在

registerKnownImplementations

或程式的其他一些地方使用執行注冊

Rx

實作的代理。

然而

RxImagePickerDelegateProxy

沒有實作

registerKnownImplementations

函數,也沒有再父類中注冊,是以選擇在程式啟動時的

appDidFinishLaunching

中注冊

RxImagePickerDelegateProxy

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        RxImagePickerDelegateProxy.register { (p) -> RxImagePickerDelegateProxy in
            return RxImagePickerDelegateProxy(imagePicker: p)
        }
        
        return true
    }
           

資料綁定

/// 拍照是否可用
        cameraButton.isEnabled = UIImagePickerController.isSourceTypeAvailable(.camera)
        
        cameraButton.rx.tap.flatMapLatest { [weak self] _ in
            return UIImagePickerController.rx.createWithParent(parent: self, animated: true) { (picker) in
                picker.sourceType = .camera
                picker.allowsEditing = false
            }.flatMap { $0.rx.didFinishPickingMediaWithInfo }.take(1)
        }.map { $0[.originalImage] as? UIImage }.bind(to: imageView.rx.image).disposed(by: bag)
        
        galleryButton.rx.tap.flatMapLatest { [weak self] (_) in
            return UIImagePickerController.rx.createWithParent(parent: self) { (picker) in
                picker.sourceType = .photoLibrary
                picker.allowsEditing = false
                }.flatMap { $0.rx.didFinishPickingMediaWithInfo }.take(1)
        }.map { $0[.originalImage] as? UIImage }.bind(to: imageView.rx.image).disposed(by: bag)
        
        cropButton.rx.tap.flatMapLatest { [weak self] (_) in
            return UIImagePickerController.rx.createWithParent(parent: self) { (picker) in
                picker.sourceType = .photoLibrary
                picker.allowsEditing = true
                }.flatMap { $0.rx.didFinishPickingMediaWithInfo }.take(1)
        }.map { $0[.editedImage] as? UIImage }.bind(to: imageView.rx.image).disposed(by: bag)