Avatar ·

Как скрыть navigation bar на первом экране в Swift, но показывать на остальных?

Всем привет! Я новичок в Swift и iOS разработке. Столкнулся с проблемой при работе с UINavigationController. У меня есть приложение с несколькими экранами. Первый экран (ViewController) — это что-то вроде splash-экрана или onboarding, где не нужен navigation bar. А на втором и последующих экранах navigation bar должен быть виден (с кнопкой "Назад" и заголовком). Я пробовал использовать `self.navigationController?.navigationBar.isHidden = true` в viewDidLoad первого контроллера. Это работает, когда экран только загружается. Но когда я перехожу на второй экран и потом возвращаюсь назад, navigation bar остаётся скрытым на первом экране, что мне и нужно. Однако, если я свайпаю назад (жестом) на первом экране, navigation bar иногда моргает или ведёт себя странно. Вот мой текущий код: ```swift class FirstViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() navigationController?.navigationBar.isHidden = true } } class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() navigationController?.navigationBar.isHidden = false } } ``` Проблема в том, что при возвращении с SecondViewController на FirstViewController с помощью свайпа, navigation bar на долю секунды появляется, а потом исчезает. Это выглядит некрасиво. Как правильно скрыть navigation bar только на первом экране, чтобы анимация была плавной и без глюков? Стоит ли использовать методы `viewWillAppear` и `viewWillDisappear`? Или есть более правильный способ? Заранее спасибо за помощь! <<>>

Правильное решение: управление видимостью navigation bar в методах жизненного цикла

Проблема, которую вы описали, связана с тем, что viewDidLoad вызывается только один раз за время жизни контроллера. Когда вы возвращаетесь на первый экран, этот метод не вызывается снова, поэтому вы не управляете видимостью navigation bar в момент перехода. Решение — использовать методы viewWillAppear и viewWillDisappear, которые вызываются каждый раз, когда экран появляется или исчезает.

Основной подход: переопределение методов жизненного цикла

Вот правильная реализация для вашего случая:

class FirstViewController: UIViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Скрываем navigation bar при появлении первого экрана
        navigationController?.setNavigationBarHidden(true, animated: animated)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Показываем navigation bar, когда уходим с первого экрана
        navigationController?.setNavigationBarHidden(false, animated: animated)
    }
}

class SecondViewController: UIViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Убеждаемся, что navigation bar виден на втором экране
        navigationController?.setNavigationBarHidden(false, animated: animated)
    }
}

Почему это работает

  • Метод viewWillAppear: вызывается каждый раз, когда экран становится видимым (включая возврат с другого экрана).
  • Метод viewWillDisappear: вызывается перед тем, как экран исчезнет (например, при переходе на второй экран).
  • Параметр animated: мы передаём его в setNavigationBarHidden, чтобы анимация скрытия/показа была согласована с анимацией перехода между экранами. Это устраняет "моргание" и делает переход плавным.

Дополнительные советы

Если вы используете UINavigationController с root view controller, который должен быть всегда без navigation bar, а все дочерние — с bar, можно настроить это в самом navigation controller, делегируя логику:

class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }
    
    func navigationController(_ navigationController: UINavigationController, 
                              willShow viewController: UIViewController, 
                              animated: Bool) {
        // Скрываем bar только для первого (root) контроллера
        let isRoot = viewController == viewControllers.first
        setNavigationBarHidden(isRoot, animated: animated)
    }
}

Этот подход централизует логику и избавляет вас от необходимости писать код в каждом контроллере. Просто используйте CustomNavigationController в storyboard или при создании navigation controller программно.

Важные замечания

  • Не используйте viewDidLoad для управления видимостью navigation bar, так как он вызывается один раз.
  • Всегда используйте setNavigationBarHidden(_:animated:) вместо прямого присваивания isHidden — это даёт плавную анимацию.
  • Если у вас есть жест свайпа назад (интерактивный pop), метод viewWillAppear всё равно будет вызван корректно, и анимация будет синхронизирована.
<<>>

Альтернативный подход: использование UINavigationBarAppearance (iOS 13+)

Если вам нужно не просто скрыть, а сделать navigation bar прозрачным или полностью изменить его внешний вид, можно использовать UINavigationBarAppearance. Этот способ полезен, когда вы хотите сохранить место для status bar, но убрать визуальные элементы.

class FirstViewController: UIViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let appearance = UINavigationBarAppearance()
        appearance.configureWithTransparentBackground() // Полностью прозрачный
        appearance.shadowColor = .clear // Убираем тень/линию
        
        navigationController?.navigationBar.standardAppearance = appearance
        navigationController?.navigationBar.scrollEdgeAppearance = appearance
        navigationController?.navigationBar.isHidden = false // Bar остаётся, но невидим
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Восстанавливаем стандартный вид
        let appearance = UINavigationBarAppearance()
        appearance.configureWithDefaultBackground()
        navigationController?.navigationBar.standardAppearance = appearance
        navigationController?.navigationBar.scrollEdgeAppearance = appearance
    }
}

Когда это полезно

  • Если на первом экране есть контент, который должен быть под status bar (например, полноэкранное изображение).
  • Если вы хотите сохранить возможность нажатия на элементы в области navigation bar (например, кастомные кнопки).
  • Если стандартное скрытие bar вызывает проблемы с layout (например, контент не поднимается вверх).

Этот способ более гибкий, но требует больше кода. Для простого скрытия/показа лучше использовать первый ответ.