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)
}