THERAMPAGE
THERAMPAGE
THERAMPAGE
Switch to the English version
Main   |   Blog   |   EngRead   |   Dragon: The Eater   |   Rampage CMS
Неблокирующая презентация вью-контроллеров
Иногда в приложении возникает ситуация, когда несколько параллельных процессов приходят к тому, что каждому из них нужно отобразить какой-то вью-контроллер. Например, оба процесса асинхронно запрашивают с сервера какие-то данные. Один - версию приложения, второй - информацию для показа пользователю. Если есть новая версия, то первый процесс незамедлительно показывает диалоговое окно с предложением обновиться. Второй процесс открывает окно с информацией когда получены данные.
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)
}