0

I have a dispatch async where I expect 4 alerts pop up on the screen..and then each get dismissed before a new alert is to be shown. (I've set a 3 second delay in between my Alert)

class ViewController: UIViewController {

    var counter = 1

    override func viewDidLoad() {
        super.viewDidLoad()

        for _ in 1...4{
            DispatchQueue.main.asyncAfter(deadline: .now() + 3.0 * Double(counter)  , execute: {
                print("called")
                self.showAlert()
            })
        }
    }

    func showAlert(){

        let alert = UIAlertController(title: "sampleTitle \(counter)", message: "sampleMessage \(counter)", preferredStyle: .alert)
        let action = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(action)

        counter += 1
        if self.presentedViewController != nil {
            dismiss(animated: true, completion: nil)
            UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
        }else{
            UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
        }
    }
}

Problem1: But for some reason the entire for loop is executed without any delay in between. I'm guessing I'm not understanding something about main queue being a serial queue.

Problem2: And I also get the following logs in my console, even though I'm dismissing the presentedViewController.

called
called
called
called
2017-06-26 11:10:57.000 topViewAndAlertTest[3360:210226] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fe630c03350> while a presentation or dismiss is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to present <UIAlertController: 0x7fe630c04180> on <UIAlertController: 0x7fe630f06fe0> while a presentation is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fe630c03350> while a presentation or dismiss is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to present <UIAlertController: 0x7fe630c06b40> on <UIAlertController: 0x7fe630f06fe0> while a presentation is in progress!

FYI My topviewcontroller is using the code from this answer

Problem3: Only 2 alerts pop...I never see the 3rd, 4th alerts!


EDIT:

After rmaddy's suggestion, my errors are slightly changed:

called
called
called
called
2017-06-26 11:59:33.417 topViewAndAlertTest[4834:441163] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fb596d05a30> while a presentation or dismiss is in progress!
2017-06-26 11:59:33.417 topViewAndAlertTest[4834:441163] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fb596d05a30> while a presentation or dismiss is in progress!

I get 2 less warnings. But still: As soon as alert 1 is on screen, alert 2 dismisses it and that's it! No delay no 3rd,4th alert!

mfaani
  • 33,269
  • 19
  • 164
  • 293

1 Answers1

2

Details

xCode 8.3.2, Swift 3.1

Full Code

import UIKit

class ViewController: UIViewController {

    var counter = 1

    private var alertViewController: UIAlertController?

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue.global(qos: .utility).async {
            for _ in 1...4 {
                sleep(2)
                DispatchQueue.main.async {
                    print("called")
                    self.showAlert()
                }
            }
        }
    }

    private func createAlertView() -> UIAlertController {
        let alertViewController = UIAlertController(title: "sampleTitle \(counter)", message: "sampleMessage \(counter)", preferredStyle: .alert)
        let action = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertViewController.addAction(action)
        return alertViewController
    }

    func showAlert(){

        let presentAlert = {
            DispatchQueue.main.async { [weak self] in
                if let _self = self {
                    _self.alertViewController = _self.createAlertView()
                    UIApplication.topViewController()?.present(_self.alertViewController!, animated: true, completion: nil)
                }
            }
        }

        DispatchQueue.main.async { [weak self] in
            if let alertViewController = self?.alertViewController {
                alertViewController.dismiss(animated: true) {
                    presentAlert()
                }
            } else {
                presentAlert()
            }
            self?.counter += 1
        }
    }
}

extension UIApplication {
    class func topViewController(base: UIViewController? = (UIApplication.shared.delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • upvoted. This works. But I'm also trying to figure out why mine doesn't work. Any thoughts on why it doesn't? – mfaani Jun 27 '17 at 17:30
  • In viewDidLoad you was trying to create 4 events with different time, but your code generate 4 action almost at the same time. Try to use this sample (the time difference between actions)`var previousTime = DispatchTime(uptimeNanoseconds: 0) for _ in 1...4{ let time = DispatchTime.now() + 3.0 * Double(counter) if previousTime.uptimeNanoseconds != 0 { print("different: \(Double(time.uptimeNanoseconds - previousTime.uptimeNanoseconds)/100_000.0) uSeconds") } previousTime = time }` – Vasily Bodnarchuk Jun 27 '17 at 17:49
  • We can try many different solutions :D I just don't understand why my code creates 4 actions at the same time. Can you explain that? – mfaani Jun 27 '17 at 17:52
  • Because your function viewDidLoad and loop inside that function was finished before first closure DispatchQueue.main.asyncAfter was started. So, let separate your code [`.now() + 3.0 * Double(counter)`]. `3.0 * Double(counter)` - this will not change in viewDidLoad (it will have the same value in viewDidLoad), `.now()` will have less the 1 second difference. This means, that in your loop `.now() + 3.0 * Double(counter)` - one time period – Vasily Bodnarchuk Jun 27 '17 at 18:00