Avatar ·

Как отловить нажатие кнопки «Назад» в UINavigationController и выполнить свой код?

Привет! Я новичок в iOS-разработке на Swift, и у меня возникла проблема. В моём приложении есть UINavigationController с несколькими экранами. Мне нужно выполнить определённое действие (например, сохранить данные или показать предупреждение), когда пользователь нажимает стандартную кнопку «Назад» в навигационной панели. Я пробовал переопределить метод `viewWillDisappear`, но он вызывается не только при нажатии «Назад», но и при любом переходе на другой экран (например, при push'е нового контроллера). Это не подходит. Вот пример моего кода: ```swift class SecondViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // Этот код срабатывает и при нажатии "Назад", и при переходе вперёд print("Экран исчезает") } } ``` Я читал, что можно использовать `UIBarButtonItem` и создать свою кнопку «Назад», но тогда теряется стандартный стиль и анимация. Как правильно перехватить именно нажатие на родную кнопку «Назад»? Заранее спасибо за помощь! <<>>

Решение через делегат UINavigationController

Самый правильный способ — подписаться на протокол UINavigationControllerDelegate и реализовать метод navigationController(_:willShow:animated:). Этот метод вызывается перед каждым показом контроллера, и вы можете проверить, является ли исчезающий контроллер вашим.

Вот пошаговая инструкция:

  1. Сделайте ваш контроллер делегатом навигационного контроллера (лучше в viewDidLoad).
  2. Реализуйте метод navigationController(_:willShow:animated:).
  3. Внутри метода проверьте, исчезает ли ваш контроллер (т.е. новый контроллер не равен текущему).
  4. Выполните нужное действие.

Пример кода:

class SecondViewController: UIViewController, UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Устанавливаем делегат
        navigationController?.delegate = self
    }
    
    // UINavigationControllerDelegate
    func navigationController(_ navigationController: UINavigationController, 
                              willShow viewController: UIViewController, 
                              animated: Bool) {
        // Проверяем, что мы уходим с текущего экрана
        guard viewController != self else { return }
        
        // Если новый контроллер не равен self — значит, мы нажали "Назад"
        // или произошёл pop-переход
        print("Пользователь нажал кнопку «Назад»!")
        // Здесь ваш код: сохранение данных, показ алерта и т.д.
    }
}

Важно: Этот метод вызывается для любого pop-перехода, включая swipe-жест назад. Если вам нужно отличить именно кнопку «Назад» от свайпа, используйте дополнительную проверку свойства isMovingFromParent или isBeingDismissed.

Более точный вариант с проверкой:

func navigationController(_ navigationController: UINavigationController, 
                          willShow viewController: UIViewController, 
                          animated: Bool) {
    guard viewController != self else { return }
    
    // Проверяем, что контроллер действительно уходит из стека
    if self.isMovingFromParent {
        print("Нажата кнопка «Назад» или сделан свайп назад")
        // Ваш код
    }
}

Альтернатива: кастомная кнопка «Назад» с сохранением стиля

Если вам нужно полностью контролировать поведение (например, показывать алерт перед уходом), можно заменить стандартную кнопку на свою, но сохранить её внешний вид:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Создаём кастомную кнопку с системным изображением
    let backButton = UIBarButtonItem(
        image: UIImage(systemName: "chevron.left"),
        style: .plain,
        target: self,
        action: #selector(backButtonTapped)
    )
    navigationItem.leftBarButtonItem = backButton
}

@objc func backButtonTapped() {
    // Ваше действие
    print("Кастомная кнопка «Назад» нажата")
    
    // Возвращаемся назад
    navigationController?.popViewController(animated: true)
}

Этот способ даёт полный контроль, но теряется стандартная анимация перехода (если не использовать popViewController).

<<>>

Использование свойства isMovingFromParent

Более простой и лаконичный подход — переопределить метод viewWillDisappear с проверкой свойства isMovingFromParent. Это свойство возвращает true, если контроллер удаляется из стека навигации (т.е. происходит именно pop-переход, а не push).

Пример:

class SecondViewController: UIViewController {
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        if self.isMovingFromParent {
            // Пользователь нажал «Назад» или сделал свайп
            print("Выполняем действие при уходе с экрана")
            // Сохраняем данные, отправляем аналитику и т.д.
        }
    }
}

Плюсы:

  • Минимум кода — не нужно реализовывать делегат.
  • Работает как для кнопки «Назад», так и для свайп-жеста.
  • Не ломает стандартное поведение навигации.

Минусы:

  • Не позволяет предотвратить уход с экрана (например, показать алерт с подтверждением). Для этого нужно использовать navigationController(_:shouldPop:) или кастомную кнопку.
  • Вызывается также при программном pop-переходе (через popViewController).

Этот метод идеален, если вам нужно просто выполнить действие при уходе (например, сохранить черновик), без блокировки навигации.