Иногда в приложении возникает ситуация, когда несколько параллельных процессов приходят к тому, что каждому из них нужно отобразить какой-то вью-контроллер. Например, оба процесса асинхронно запрашивают с сервера какие-то данные. Один - версию приложения, второй - информацию для показа пользователю. Если есть новая версия, то первый процесс незамедлительно показывает диалоговое окно с предложением обновиться. Второй процесс открывает окно с информацией когда получены данные.
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()
Запустив этот код можно легко убедиться, что всегда отображается сообщение о новой версии и никогда не выполняется переход на другой экран. Это происходит из-за того, что система просто игнорирует второй и все последующие запросы на отображение других вью-контроллеров, если какой-то один уже отображается. Дело в том, что UIViewController есть свойство presentedViewController куда записывается первый и единственно возможный в момент времени презентованный вью-контроллер и хранится там пока не будет освобожден. Что можно сделать в такой ситуации. Ну например дождаться завершения открытого вью-контроллера и затем уже отобразить следующий.
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)
}
}
}
Пример использования:
safePresent { [weak self] in
self?.present(controller, animated: true)
}