美文网首页
16.13Todo VI - 更好的处理授权提示

16.13Todo VI - 更好的处理授权提示

作者: CDLOG | 来源:发表于2019-07-17 18:08 被阅读0次

    在这一节,我们修复第一次打开图片选择界面由于授权导致的白屏问题。大家可以在这里下载项目的初始模板,唯一的改动,就是在Flash.swift里,我们把flash变成了UIViewControllerextension。因为稍后,我们要在不同的view controller中使用它。

    把用户的授权结果变成一个Observable

    之前我们说过,iOS的用户授权动作是异步的。因此,为了能在用户完成授权操作之后继续更新UI,我们得先把授权的结果封装成一个Observable。实际上,这个Observable只可能是下面三种情况:

    refactor photo chooser
    • 如果用户已授权,事件序列就是:.next(true).completed()
    • 如果用户未授权,序列的第一个事件就一定是.next(false)。然后,如果用户拒绝授权,序列中的事件就是:.next(false).completed。否则,就是.next(true).completed

    有了这个思路之后,我们在新添加的PHPhotoLibrary+Rx.swift中添加下面的代码:

    extension PHPhotoLibrary {
        static var isAuthorized: Observable<Bool> {
            return Observable.create { observer in
                DispatchQueue.main.async {
                    if authorizationStatus() == .authorized {
                        observer.onNext(true)
                        observer.onCompleted()
                    } else {
                        // Notify the subscriber of the authorization failure
                        // event explicitly. We may omit this event by default.
                        observer.onNext(false)
                        requestAuthorization {
                            observer.onNext($0 == .authorized)
                            observer.onCompleted()
                        }
                    }
                }
                return Disposables.create()
            }
        }
    }
    
    

    其中,用到了PHPhotoLibrary的两个API:

    • authorizationStatus获取当前授权状态;
    • requestAuthorization申请用户授权;

    至于为什么要把通知observer的代码放在DispatchQueue.main.async里,是为了避免在自定义的事件序列中影响其它Observable的订阅,甚至是把整个UI卡住。理解了这些之后,上面的代码就很好理解了,就是之前那个事件流的代码表示。接下来,我们只要订阅这个Observable就好了。

    订阅用户的授权结果

    订阅的部分,应该写在PhotoCollectionViewController.viewDidLoad方法里。先别着急,这个过程要比我们想象的复杂一点,我们不能直接订阅isAuthorizedonNext并处理true/false的情况,因为单一的事件值并不能反映真实的授权情况。按照之前分析的:

    • 授权成功的序列可能是:.next(true).completed.next(false).next(true).completed
    • 授权失败的序列则是:.next(false).next(false).completed

    因此,我们需要把isAuthorized这个事件序列处理一下,分别处理授权成功和失败的情况。

    订阅成功事件

    首先来订阅授权成功事件,我们只要忽略掉事件序列中所有的false,并读到第一个true,就可以认为授权成功了。使用“过滤型”operator可以轻松完成这个任务:

    // In PhotoCollectionViewController
    override func viewDidLoad() {
        super.viewDidLoad()
        setCellSpace()
    
        let isAuthorized = PHPhotoLibrary.isAuthorized
    
        isAuthorized
            .skipWhile { $0 == false }
            .take(1)
            .subscribe(onNext: {
                [weak self] _ in
                // Reload the photo collection view
            })
            .addDisposableTo(bag)
    }
    
    

    可以看到,上面的代码里,我们使用了skipWhiletake模拟了忽略所有false并读取第一个true这个动作。然后,在.next事件的订阅里,我们直接更新UI就好了:

    // Inside subscribe(onNext: )
    // Reload the photo collection view
    if let `self` = self {
        self.photos = PhotoCollectionViewController.loadPhotos()
    
        DispatchQueue.main.async {
            self.collectionView?.reloadData()
        }
    }
    
    

    这次,为什么又使用了DispatchQueue.main.async呢?因为,当我们调用requestAuthorization请求用户授权时,在这个API的说明中,我们可以找到这样一段话:

    Photos may call your handler block on an arbitrary serial queue. If your handler needs to interact with UI elements, dispatch such work to the main queue.

    也就是说,我们传递给requestAuthorization的closure参数有可能并不在主线程中执行,一旦如此,我们订阅的授权结果的代码也就不会在主线程中执行。但是,由于我们在订阅中更新了UI,如果这个代码不在主线程中,App就会立即闪退了。因为,需要人为保证它执行在主线程里。

    不过,实际上,我们并不经常在RxSwift的订阅代码里使用GCD,RxSwift提供了一个更简单的机制,叫做scheduler,在后面的内容里,我们会专门提到它。现在可以先体验下它的用法,为了保证订阅代码一定执行在主线程,我们可以把之前的代码改成这样:

    isAuthorized
        .skipWhile { $0 == false }
        .take(1)
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: {
            [weak self] _ in
            // Reload the photo collection view
            if let `self` = self {
                self.photos = PhotoCollectionViewController.loadPhotos()
                self.collectionView?.reloadData()
            }
        })
        .addDisposableTo(bag)
    
    

    其中,observeOn(MainScheduler.instance)就表示了在主线程中执行订阅代码,这样的逻辑,结果和之前是一样的,只是它看起来,更RxSwift一些。现在,在模拟器里把TodoDemo删掉,重新编译、安装和执行,在iOS提示我们授权访问图片库的时候选择OK,就可以看到图片立即刷新的效果了。

    订阅失败事件

    接下来,我们处理拒绝授权的情况。这种情况相比成功简单一些,因为它对应的事件序列只有一种情况:.next(false).next(false).completed。因此,我们只要对事件序列中所有元素去重之后,订阅最后一个.next事件,如果是false,就可以确定是用户拒绝授权了。因此,在订阅成功授权的代码后面,继续添加下面的代码:

    isAuthorized
        .distinctUntilChanged()
        .takeLast(1)
        .filter { $0 == false }
        .subscribe(onNext: { [weak self] _ in
            self?.flash(title: "Cannot access your photo library",
                message: "You can authorize access from the Settings.",
                callback: { [weak self] _ in
                    self?.navigationController?.popViewController(animated: true)
                })
        })
        .addDisposableTo(bag)
    
    

    如果你还记得之前我们讲过的各种“过滤型”operator,理解上面的代码应该不会有任何困难。我们用alert view给用户显示了一个错误提示,并在用户关闭提示时,自动退回到todo编辑界面。为了看到这个错误提示,我们在Settings里,把ToDoDemo的访问授权关掉就好了:

    refactor photo chooser

    What's next?

    在这一节中,我们了解了一种典型的场景:如何用RxSwift改进需要用户授权操作的用户体验。实际上,任何一个需要授权的操作都可以采用类似的方式来处理。接下来,在进一步开发App之前,我们来看除了“过滤型”之外的另一大类operator,transform operators

    相关文章

      网友评论

          本文标题:16.13Todo VI - 更好的处理授权提示

          本文链接:https://www.haomeiwen.com/subject/yipydqtx.html