代碼下載下傳
UIImagePickerController
搭建UI
建構如下UI:
設定按鈕的是否可用:
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
檔案中配置相機、相冊權限詢問框的描述,如下所示:
因為在使用
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)