Avatar ·

Как правильно закрыть (dismiss) ViewController в Swift: модальный и push способы

Всем привет! Я новичок в iOS-разработке и пишу своё первое приложение на Swift. У меня есть два экрана: первый (MainViewController) и второй (DetailViewController). Я перехожу со второго на первый через кнопку "Назад", но ничего не работает. Вот мой код: ```swift // 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 detailVC = DetailViewController() detailVC.modalPresentationStyle = .fullScreen present(detailVC, animated: true, completion: nil) } } // DetailViewController.swift import UIKit class DetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let backButton = UIButton(type: .system) backButton.setTitle("Назад", for: .normal) backButton.addTarget(self, action: #selector(goBack), for: .touchUpInside) backButton.frame = CGRect(x: 100, y: 200, width: 200, height: 50) view.addSubview(backButton) } @objc func goBack() { // Здесь я пробовал разные варианты, но ничего не работает self.dismiss(animated: true, completion: nil) // Или navigationController?.popViewController(animated: true) } } ``` Когда я нажимаю кнопку "Назад", экран не закрывается. Я уже перепробовал и `dismiss`, и `popViewController`, но ничего не помогает. Подскажите, пожалуйста, в чём проблема и как правильно закрывать ViewController? Заранее спасибо! <<>>

Правильный способ закрытия 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. Этот метод особенно удобен, когда у вас сложная иерархия контроллеров.

Как это сделать:

  1. В MainViewController (контроллер, к которому нужно вернуться) добавьте метод:
@IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
    // Здесь можно обработать данные, если нужно
}
  1. В Storyboard свяжите кнопку "Назад" на DetailViewController с action unwindToMain. Для этого зажмите Ctrl и перетащите от кнопки на иконку "Exit" (выход) в верхней панели DetailViewController. Выберите созданный метод.

Преимущества этого подхода:

  • Не нужно писать код для закрытия контроллера — всё делает Storyboard.
  • Unwind segue автоматически определяет, какой контроллер нужно закрыть, даже если вы перешли через несколько экранов.
  • Меньше шансов ошибиться с типом перехода.

Недостаток: этот метод работает только при использовании Storyboard. Если вы создаёте контроллеры программно, то лучше использовать dismiss или popViewController.