Введение: почему эта проклятая линия не даёт спать разработчикам?
Если вы когда-нибудь пытались сделать дизайн приложения «как в Apple Music» или «как в Telegram» — вы наверняка сталкивались с этим. Вы создаёте идеальный UINavigationBar с красивым градиентом, прозрачностью или кастомным цветом, запускаете симулятор — и видите эту тонкую серую или чёрную линию (1 пиксель) внизу. Она выглядит как артефакт, как грязь на стекле. И да, она бесит.
Эта 1px линия — не баг, а фича. Это shadowImage + backgroundImage, которые Apple зачем-то оставила по умолчанию. Даже в iOS 17 она всё ещё появляется, если вы не скажете системе: «Эй, убери это». Вопрос на Stack Overflow по этой теме набрал тысячи просмотров и десятки ответов. Значит, тема реально больная.
В этой статье я покажу 3 рабочих подхода на Swift, как скрыть нижнюю линию UINavigationBar. Мы разберём как старые способы (iOS 13-), так и современные через UINavigationBarAppearance. Поехали!
Способ 1: Классика — через backgroundImage и shadowImage
Самый старый и проверенный метод. Он работает начиная с iOS 5 (да, ещё с тех времён, когда скевоморфизм был в моде). Идея простая: мы устанавливаем пустое изображение в качестве фона и тени.
Шаг 1: Создаём пустое изображение
extension UIImage {
static func pixelImage(withColor color: UIColor = .clear) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
}
Шаг 2: Применяем к navigationBar
func hideBottomLine() {
let emptyImage = UIImage.pixelImage()
navigationController?.navigationBar.setBackgroundImage(emptyImage, for: .default)
navigationController?.navigationBar.shadowImage = emptyImage
}
Плюсы: Работает везде, даже на старых iOS.
Минусы: Не всегда корректно работает с scrollEdgeAppearance и большими заголовками. Если у вас есть UISearchBar — линия может вернуться.
Способ 2: Современный — через UINavigationBarAppearance (iOS 13+)
Начиная с iOS 13 Apple представила UINavigationBarAppearance. Это единый объект, который управляет всеми состояниями: стандартным, скроллом и компактным. Идеально для тех, кто хочет «один раз настроил — и забыл».
Полный пример для UIViewController
func setupTransparentNavigationBar() {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = .clear
appearance.shadowColor = .clear // убирает линию
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.compactAppearance = appearance
}
Обратите внимание: shadowColor = .clear — это ключевая строка. Если её не написать, линия останется. configureWithTransparentBackground() делает фон прозрачным, но не трогает тень.
Если вам нужен цветной фон, но без линии:
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemBlue
appearance.shadowColor = .clear
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
Плюсы: Чистый код, легко комбинировать с большими заголовками и поиском.
Минусы: iOS 13+ только. Если проект поддерживает iOS 12 — придётся вернуться к первому способу.
Способ 3: Хардкор — через searchBar и делегаты
Бывает, что линия появляется только при активации UISearchController. Это отдельная боль. Вот решение специально для этого случая.
Прячем линию при активном поиске
class SearchNavController: UINavigationController, UISearchControllerDelegate {
func searchController(_ searchController: UISearchController,
willPresentSearchController: UISearchController) {
hideBottomLine()
}
func searchController(_ searchController: UISearchController,
didDismissSearchController: UISearchController) {
restoreBottomLine()
}
private func hideBottomLine() {
let empty = UIImage()
navigationBar.setBackgroundImage(empty, for: .default)
navigationBar.shadowImage = empty
}
private func restoreBottomLine() {
navigationBar.setBackgroundImage(nil, for: .default)
navigationBar.shadowImage = nil
}
}
Плюсы: Тонкий контроль, работает как часы.
Минусы: Много кода, легко забыть восстановить линию после скролла.
Сравнение подходов: что выбрать?
Давайте сведём всё в таблицу (мысленно, но вы поняли):
- Способ 1 — для legacy-проектов (iOS 12 и ниже). Просто, но может сломаться на iPad с Split View.
- Способ 2 — современный стандарт. Используйте его, если ваше приложение поддерживает iOS 13+. Это рекомендация Apple.
- Способ 3 — для экстренных случаев, когда стандартные методы не помогают (например, при кастомном
UISearchBar).
Как проверить, что линия исчезла?
Самый простой способ — запустить симулятор и посмотреть на границу между UINavigationBar и view. Если линия всё ещё видна — скорее всего, вы забыли применить appearance ко всем состояниям. Проверьте:
print(navigationController?.navigationBar.standardAppearance.shadowColor)
print(navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor)
Если оба nil или .clear — линия ушла. Если нет — ищите, где переопределяется appearance (например, в UIScrollViewDelegate).
Бонус: Как убрать линию в SwiftUI?
Если вы используете SwiftUI, то NavigationView тоже имеет эту линию. Убрать её можно через .toolbarBackground:
NavigationView {
ContentView()
.toolbarBackground(.hidden, for: .navigationBar)
}
Или через UIToolbar.appearance(), если вам нужен глобальный эффект. Но это уже тема для отдельной статьи.
Что делать, если ничего не помогло?
Иногда линия возвращается из-за того, что вы используете кастомный UINavigationController (например, UISplitViewController на iPad).