Search code examples
swiftrx-swift

Swift UIAlert doesn't waiting user response


func permissionInit() {
  Task{
        addListViewModel?.cameraPermission.accept(await chkCameraPermission())
            addListViewModel?.photoLibraryPermission.accept(await chkPhotoLibraryPermission())
            addListViewModel?.motionPermission.accept(await chkMotionPermission())
        }
    }

private func chkCameraPermission() async -> Bool{
        let mediaType = AVMediaType.video
        await AVCaptureDevice.requestAccess(for: mediaType)
        let mediaAuthoriztionStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
        switch mediaAuthoriztionStatus{
        case .authorized:
            print("ShopUp Camera Permission True")
            return true
        case .denied, .restricted, .notDetermined:
            print("ShopUp Camera Permission False")
            warningAlert(title: "권한 오류", infoMativeMsg: "")
            return false
        default:
            return false
        }
    }
    
    private func chkPhotoLibraryPermission() async -> Bool {
        let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
        switch status {
        case .authorized:
            print("ShopUp Photo Permission True")
            return true
        case .denied, .restricted, .notDetermined:
            print("ShopUp Photo Permission False")
            warningAlert(title: "권한 오류", infoMativeMsg: "")
            return false
        default:
            return false
        }
    }
    
    private func chkMotionPermission() async -> Bool{
        let coreMotionGranted = CMPedometer.authorizationStatus()
        switch coreMotionGranted {
        case .authorized:
            print("ShopUp CoreMotion Permission True")
            return true
        case .notDetermined, .restricted, .denied :
            print("ShopUp CoreMotion Permission False")
            warningAlert(title: "권한 오류", infoMativeMsg: "")
            return false
        default:
            return false
        }
    }
    
    func warningAlert(title: String, infoMativeMsg: String, completionHandler: Void? = nil) {
        let alert = UIAlertController(title: title, message: infoMativeMsg, preferredStyle: .alert)
        if completionHandler != nil {
            let okAction = UIAlertAction(title: "확인", style: .default, handler: {_ in completionHandler})
            alert.addAction(okAction)
        }else {
            let okAction = UIAlertAction(title: "확인", style: .default)
            alert.addAction(okAction)
        }
        self.present(alert, animated: true, completion: completionHandler != nil ? {completionHandler!} : nil)
    }

I added UIAlert in ViewController but it doesn't wait user response and showing error.

I also tried await on self.present but not working too. permissionInit has an await but it doesn't seem to work.

2023-01-09 14:45:37.015435+0900 ShopUp[544:94537] [Presentation] Attempt to present <UIAlertController: 0x12c03e000> on <UINavigationController: 0x12d019c00> (from <ShopUp.AddListViewController: 0x12ce08350>) while a presentation is in progress.
2023-01-09 14:45:37.015644+0900 ShopUp[544:94537] [Presentation] Attempt to present <UIAlertController: 0x12d07b800> on <UINavigationController: 0x12d019c00> (from <ShopUp.AddListViewController: 0x12ce08350>) while a presentation is in progress.

I would like to show UIAlert in oder.

I would be grateful if you could let me know which part is wrong.


Solution

  • You are calling three functions, all of which try to present an alert at the same time (or nearly so.) Only the first one succeeds because a view controller can only present a single other view controller. The other two fail which is why you get the two error messages.

    Here is one way to concatenate the three requests so that each one will wait until the previous ones are complete:

    extension UIViewController {
        func permissionInit() {
            let avCapture = Observable.createAsync { await AVCaptureDevice.requestAccess(for: .video) }
                .filter { !$0 }
                .observe(on: MainScheduler.instance)
                .flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
    
            let phPhoto = Observable.createAsync { await PHPhotoLibrary.requestAuthorization(for: .readWrite) }
                .filter { $0 != .authorized }
                .observe(on: MainScheduler.instance)
                .flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
    
            let cmPedo = Observable.just(CMPedometer.authorizationStatus())
                .filter { $0 != .authorized }
                .flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
    
            _ = Observable.concat(avCapture, phPhoto, cmPedo)
                .subscribe(onNext: {
                    print("all requests complete.")
                })
        }
    
        func warningAlert(title: String, infoMativeMsg: String) -> Observable<Void> {
            Observable.deferred {
                let result = PublishSubject<Void>()
                let alert = UIAlertController(title: title, message: infoMativeMsg, preferredStyle: .alert)
                let okAction = UIAlertAction(title: "확인", style: .default, handler: { _ in result.onSuccess(()) })
                alert.addAction(okAction)
                self.present(alert, animated: true, completion: nil)
                return result
            }
        }
    }
    
    extension Observable {
        static func createAsync(_ asyncFunc: @escaping () async throws -> Element) -> Observable<Element> {
            Observable.create { observer in
                let task = Task {
                    do {
                        observer.onSuccess(try await asyncFunc())
                    } catch {
                        observer.onError(error)
                    }
                }
                return Disposables.create { task.cancel() }
            }
        }
    }
    
    public extension ObserverType {
        func onSuccess(_ element: Element) -> Void {
            onNext(element)
            onCompleted()
        }
    }
    

    The key is in using the concat operator which will only subscribe to one Observable at a time. It waits until an Observable stops before subscribing to the next one.

    Learn more in this article: Recipes for Combining Observables in RxSwift

    Lastly, I also agree with HangarRash.