How to Hide the UINavigationBar 1px Bottom Line

How to Hide the UINavigationBar 1px Bottom Line

Written by

in

Quick answer: To hide the 1px bottom line under a UINavigationBar in iOS 13 and later, create a UINavigationBarAppearance instance, set its shadowColor to .clear, and assign it to both standardAppearance and scrollEdgeAppearance. For per-screen control, assign the appearance to navigationItem instead of the shared navigationBar.

Why this happens

By default, UINavigationBar draws a 1-point (or 1-pixel on non-Retina displays) hairline shadow along its bottom edge. UIKit adds this divider to visually separate the navigation bar from the underlying view controller or scrollable content.

When your design calls for the navigation bar to blend seamlessly into a matching header or background color, that default hairline stands out. Starting in iOS 13, Apple moved navigation bar styling—including this bottom shadow—into UINavigationBarAppearance, which means older shadowImage tweaks no longer work reliably on their own.

How to fix it

1. Use UINavigationBarAppearance (iOS 13 and later)

The official way to remove the bottom line in modern iOS is to set shadowColor to .clear (or nil) on a UINavigationBarAppearance object.

A common mistake is updating only scrollEdgeAppearance or only standardAppearance. If you only set one, the 1px line will disappear at rest and then suddenly reappear as soon as the user scrolls a UIScrollView or UITableView underneath the bar. Assign your appearance to both standardAppearance and scrollEdgeAppearance (and compactAppearance if you support landscape compact bars):

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        configureNavigationBar()
    }

    private func configureNavigationBar() {
        guard let navigationBar = navigationController?.navigationBar else { return }

        let appearance = UINavigationBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.backgroundColor = .systemBackground
        appearance.shadowColor = .clear

        navigationBar.standardAppearance = appearance
        navigationBar.scrollEdgeAppearance = appearance
        navigationBar.compactAppearance = appearance
    }
}

Choose the configuration method that matches the bar style you want before setting shadowColor = .clear:

  • configureWithOpaqueBackground(): Uses a solid background color with no translucency.
  • configureWithDefaultBackground(): Keeps the standard translucent blur effect.
  • configureWithTransparentBackground(): Makes the bar background completely transparent.

If you want the 1px line hidden across your entire app, apply the same configuration to UINavigationBar.appearance() inside AppDelegate or SceneDelegate.

2. Hide the line on a single view controller using navigationItem

Modifying navigationController?.navigationBar changes the bar for every view controller in the navigation stack. If you only want to hide the bottom line on one screen—and have it automatically return on other screens—assign the appearance to navigationItem instead.

UINavigationItem has its own standardAppearance, scrollEdgeAppearance, and compactAppearance properties. UIKit automatically swaps them in when that view controller is active:

import UIKit

class SeamlessHeaderViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let appearance = UINavigationBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.backgroundColor = .systemIndigo
        appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        appearance.shadowColor = .clear

        navigationItem.standardAppearance = appearance
        navigationItem.scrollEdgeAppearance = appearance
        navigationItem.compactAppearance = appearance
    }
}

Using navigationItem avoids manual state cleanup in viewWillDisappear(_:) and prevents visual glitches during interactive back-swipe gestures.

3. Legacy fallback for iOS 12 and earlier

If your deployment target still includes iOS 12 or earlier, UINavigationBarAppearance is not available. On older iOS versions, UINavigationBar only honors a custom shadowImage when you also provide a custom background image:

if #available(iOS 13.0, *) {
    let appearance = UINavigationBarAppearance()
    appearance.configureWithDefaultBackground()
    appearance.shadowColor = .clear
    navigationItem.standardAppearance = appearance
    navigationItem.scrollEdgeAppearance = appearance
} else {
    let navigationBar = navigationController?.navigationBar
    navigationBar?.setBackgroundImage(UIImage(), for: .default)
    navigationBar?.shadowImage = UIImage()
}

Keep in mind that passing an empty UIImage() to setBackgroundImage(_:for:) removes the bar’s default blur translucency. If you need a solid color without translucency on iOS 12, set navigationBar?.isTranslucent = false and navigationBar?.barTintColor = .red alongside the empty images.

If you must preserve translucency on iOS 12, the traditional workaround is to locate the hairline UIImageView (height <= 1) inside navigationBar.subviews and toggle isHidden = true in viewWillAppear(_:) and false in viewWillDisappear(_:). However, traversing internal UIKit subviews is fragile across OS updates, so wrap it inside an else branch for iOS 12 only.

Avoid using key-value coding hacks like navigationBar.setValue(true, forKey: "hidesShadow"). That relies on an undocumented internal property that can break in future iOS releases or cause App Review issues.

How to prevent it

  • Always set both standardAppearance and scrollEdgeAppearance: Setting only one causes the hairline to appear or disappear unexpectedly when scrolling starts or stops.
  • Prefer navigationItem for screen-specific styling: Assigning appearance objects to navigationItem keeps single-screen customizations isolated without polluting the shared UINavigationBar.
  • Set shadowColor = .clear after calling configureWith...: Calling configureWithOpaqueBackground(), configureWithDefaultBackground(), or configureWithTransparentBackground() resets shadowColor to its default value, so always set shadowColor = .clear afterward.
  • Use UINavigationBar.appearance() for app-wide rules: Configure global defaults once at launch instead of repeating the setup in every view controller.

FAQ

Why does the bottom line reappear when I scroll?

UIKit switches between scrollEdgeAppearance (when content is at the top) and standardAppearance (when content scrolls underneath the bar). Make sure shadowColor = .clear is set on both appearance objects.

Can I change the color of the 1px line instead of hiding it?

Yes. Set appearance.shadowColor = .systemRed (or any UIColor) on your UINavigationBarAppearance instance instead of .clear.

Does setting shadowImage = UIImage() still work on modern iOS?

No, not reliably. Since iOS 13, UINavigationBarAppearance takes precedence over legacy properties like shadowImage and setBackgroundImage(_:for:), so you should use UINavigationBarAppearance for all iOS 13+ targets.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.