Как правильно закрыть (dismiss) ViewController в Swift: модальный и push способы
Правильный способ закрытия ViewController в Swift
Ваша проблема типична для начинающих разработчиков. Дело в том, что dismiss и popViewController используются в разных ситуациях, и их нельзя применять одновременно или путать. Давайте разберёмся.
1. Модальный переход (present/dismiss)
Когда вы используете present(_:animated:completion:), вы показываете новый ViewController модально. Чтобы его закрыть, нужно вызвать dismiss(animated:completion:) у самого себя или у родительского контроллера. В вашем коде метод goBack вызывается из DetailViewController, поэтому self.dismiss(animated:true, completion:nil) — это правильный способ. Но почему он не работает?
Проблема в том, что вы создаёте DetailViewController без использования Storyboard или XIB. Если вы не настраиваете его view должным образом, то при вызове present контроллер может не получить правильный контекст. Исправленный код:
// MainViewController.swift
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setTitle("Открыть детали", for: .normal)
button.addTarget(self, action: #selector(openDetail), for: .touchUpInside)
button.frame = CGRect(x: 100, y: 100, width: 200, height: 50)
view.addSubview(button)
}
@objc func openDetail() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let detailVC = storyboard.instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController else {
return
}
detailVC.modalPresentationStyle = .fullScreen
present(detailVC, animated: true, completion: nil)
}
}
// DetailViewController.swift
import UIKit
class DetailViewController: UIViewController {
@IBAction func goBack(_ sender: UIButton) {
// Правильный способ для модального перехода
self.dismiss(animated: true, completion: nil)
}
}
Обратите внимание: я использую Storyboard для создания контроллера. Это гарантирует, что view будет загружена правильно. Кроме того, метод dismiss вызывается у самого DetailViewController — это стандартный паттерн.
2. Push-переход (Navigation Controller)
Если вы хотите использовать навигационный стек (как в приложениях с "шагами"), то нужно обернуть первый контроллер в UINavigationController. Тогда переход делается через pushViewController, а возврат — через popViewController.
// AppDelegate или SceneDelegate
let navController = UINavigationController(rootViewController: MainViewController())
window?.rootViewController = navController
// MainViewController.swift
@objc func openDetail() {
let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)
}
// DetailViewController.swift
@IBAction func goBack(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
В этом случае dismiss не используется вообще — только popViewController.
Основные правила:
- Если вы открыли контроллер через
present— закрывайте черезdismiss. - Если вы открыли контроллер через
pushViewController— закрывайте черезpopViewController. - Не смешивайте эти методы — это приведёт к ошибкам.
- Всегда проверяйте, существует ли
navigationControllerпри использованииpopViewController.
Дополнительные советы:
- Используйте Storyboard с идентификаторами для контроллеров — это надёжнее.
- Для программного создания контроллера всегда инициализируйте его через Storyboard или XIB.
- Если вы используете модальный переход с
.fullScreen, тоdismissработает всегда, даже без Navigation Controller.
Надеюсь, теперь стало понятно! Если у вас остались вопросы — пишите в комментариях.
<<Альтернативный подход: использование unwind segue
Вместо программного вызова dismiss или popViewController можно использовать unwind segue (обратный переход) в Storyboard. Этот метод особенно удобен, когда у вас сложная иерархия контроллеров.
Как это сделать:
- В MainViewController (контроллер, к которому нужно вернуться) добавьте метод:
@IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
// Здесь можно обработать данные, если нужно
}
- В Storyboard свяжите кнопку "Назад" на DetailViewController с action
unwindToMain. Для этого зажмите Ctrl и перетащите от кнопки на иконку "Exit" (выход) в верхней панели DetailViewController. Выберите созданный метод.
Преимущества этого подхода:
- Не нужно писать код для закрытия контроллера — всё делает Storyboard.
- Unwind segue автоматически определяет, какой контроллер нужно закрыть, даже если вы перешли через несколько экранов.
- Меньше шансов ошибиться с типом перехода.
Недостаток: этот метод работает только при использовании Storyboard. Если вы создаёте контроллеры программно, то лучше использовать dismiss или popViewController.