代码下载
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
函数,用于将Any
类型数据转换为特定类型,方便后面使用:
private func castOrThrow(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.self, object: a[1])
}
}
/// 创建图片选择控制器Observable
/// - Parameters:
/// - parent: 父控制器
/// - animated: 动画
/// - configureImagePicker: 配置闭包
static func createWithParent(parent: UIViewController?, animated: Bool = true, configureImagePicker: @escaping (UIImagePickerController) throws -> Void) -> Observable {
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
文件中配置相机、相册权限询问框的描述,如下所示:
[图片上传失败...(image-f2b960-1604572278262)]
因为在使用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)