THERAMPAGE
THERAMPAGE
THERAMPAGE
Switch to the Russian version
Main   |   Blog   |   EngRead   |   Dragon: The Eater   |   Rampage CMS
Non-blocking presentation of view controllers
Sometimes a situation arises in an application when several parallel processes come to the conclusion that each of them needs to display some kind of view controller. For example, both processes asynchronously request some data from the server. One is the version of the application, the second is information to display to the user. If there is a new version, the first process immediately displays a dialog box asking you to upgrade. The second process opens a window with information when data is received.
import UIKit
import PlaygroundSupport
class Controller: UIViewController {
    override func loadView() {
        view = UIView()
        view.backgroundColor = .red
    }
    override func viewDidLoad() {
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            // request a web-server for a latest app version
            DispatchQueue.main.async {
                self.showAlert()
            }
        }
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            // request a web-server for some data
            DispatchQueue.main.async {
                self.showScreen()
            }
        }
    }
    private func showAlert() {
        let alert = UIAlertController(
            title: "New version available",
            message: nil,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Close", style: .destructive))
        present(alert, animated: true)
    }
    private func showScreen() {
        let controller = UIViewController()
        controller.view.backgroundColor = .green
        present(controller, animated: true)
    }
}
PlaygroundPage.current.liveView = Controller()
By running this code, you can easily make sure that the message about the new version is always displayed and the application never switches to another screen. This is due to the fact that the system simply ignores the second and all subsequent requests to display other view controllers if one is already displayed. The fact is that UIViewController has a property presentedViewController where the first and only possible presented view controller is written and stored there until it is released. What can be done in such a situation. Well, for example, wait for the open view controller to complete and then display the next one.
extension UIViewController {
    func safePresent(_ block: (() -> Void)?) {
        let noPresented  = nil == presentedViewController
        let noTransition = nil == transitionCoordinator
        noPresented && noTransition
            ? block?()
            : DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                [weak self] in self?.safePresent(block)
            }
    }
}
Usage example:
safePresent { [weak self] in
    self?.present(controller, animated: true)
}