0

I'm using a loading modal pretty much according to this topic:

Loading an "overlay" when running long tasks in iOS

I use the same code in several ViewControllers, so I created an extension:

extension UIViewController {
    func showLoading() {
        let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)

        let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
        loadingIndicator.hidesWhenStopped = true
        loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
        loadingIndicator.startAnimating();

        alert.view.addSubview(loadingIndicator)

        present(alert, animated: false, completion: nil)
    }

    func hideLoading() {
        if ( presentedViewController != nil && !presentedViewController!.isBeingPresented ) {
            dismiss(animated: false, completion: nil)
        }
    }
}

I typically use the code like this:

self.showLoading()
callNetwork() { response in
    DispatchQueue.main.async {
       self.hideLoading()
       ....
    }
}

If the network call takes 0.5s or more, everything works fine. The issue is if the network is too fast. Then I'll get an error similar to this one:

Warning: Attempt to dismiss from view controller <UINavigationController: 0x7ff581830a00> while a presentation or dismiss is in progress!

And the modal won't get dismissed.

The best solution I can come up with is something like this (super class instead of extension as extensions can't have variables):

class LoadingViewController: UIViewController {
    var shouldDismissImmediately = false

    func showLoading() {
        shouldDismissImmediately = false
        let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)

        let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
        loadingIndicator.hidesWhenStopped = true
        loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
        loadingIndicator.startAnimating();

        alert.view.addSubview(loadingIndicator)

        present(alert, animated: false) {
             if (self.shouldDismissImmediately) {
                 self.dismiss(animated: false, completion: nil)
             }
        }
    }

    func hideLoading() {
        if ( presentedViewController != nil && !presentedViewController!.isBeingPresented ) {
            dismiss(animated: false, completion: nil)
        } else {
           shouldDismissImmediately = true
        }
    }
}

Can anyone think of a better solution? This one just doesn't feel right. Maybe I'm doing something fundamentally wrong. Like - should I even present such a dialog when waiting for a network response? Is there a better way of making the user to wait? I need the user to be aware that something is happening and in the same time, I need him not to be able to press any buttons in the UI.

1 Answers1

0
extension UIViewController {
    func showLoading(finished: @escaping () -> Void) {
        let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)

        let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
        loadingIndicator.hidesWhenStopped = true
        loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
        loadingIndicator.startAnimating();

        alert.view.addSubview(loadingIndicator)

        present(alert, animated: false, completion: finished)
    }

    func hideLoading(finished: @escaping () -> Void) {
        if ( presentedViewController != nil && !presentedViewController!.isBeingPresented ) {
            dismiss(animated: false, completion: finished)
        }
    }
}

self.showLoading(finished: {
    callNetwork() {
        DispatchQueue.main.async {
            self.hideLoading(finished: {
                // done
             })
         }
     }
})
iOSfleer
  • 408
  • 6
  • 20