RxSwift官方实例六(UIImagePickerController)

代码下载

UIImagePickerController

搭建UI

构建如下UI:


定位UI

设置按钮的是否可用:

        cameraButton.isEnabled = UIImagePickerController.isSourceTypeAvailable(.camera)

UIImagePickerControllerDelegate的Rx实现

UIImagePickerController的代理对象需要遵守UIImagePickerControllerDelegateUINavigationControllerDelegate两个协议的:

weak open var delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?

由于RxCocoa已经实现UINavigationControllerDelegateRxNavigationControllerDelegateProxy,没有实现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文件中配置相机、相册权限询问框的描述,如下所示:
[图片上传失败...(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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,208评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,502评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,496评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,176评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,185评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,630评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,992评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,973评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,510评论 1 325
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,546评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,659评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,990评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,421评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,569评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,238评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,732评论 2 366