The Frustrating State of Navigation Bars on iOS

It’s been 18 years since iOS launched and 17 years since the iOS SDK was released—yet the navigation bar is still frustratingly uncustomizable. Sure, you can tweak text attributes, background images, and colors, but anything beyond that? Forget it. Even Apple’s own Invites app highlights how limited it is.

Take back buttons, for example. UIKit technically allows you to set an image or a custom title, but try adding a fully custom view (say, one that matches your company’s design) using UIBarButtonItem(customView:), and you’ll quickly realize it doesn’t work. Quoting the documentation:

When this navigation item is immediately below the top item in the stack, the navigation controller derives the Back button for the navigation bar from this navigation item. If you want to specify a custom title or image for the Back button, you can assign a custom bar button item (with your custom title or image) to this property. When you configure your bar button item, don’t assign a custom view to it; the navigation item ignores custom views in the backBarButtonItem.

In other words: don’t even bother trying.

A common workaround is to set a custom leftBarButtonItem instead, replacing the backBarButtonItem entirely. But doing so also breaks the interactive pop gesture (the swipe back gesture) and the back button menu. Sure, you can get both to work with a custom button, but it’s significantly harder—and almost nobody bothers because of how much effort it takes. Once you start noticing this in popular apps (including Apple’s own), you won’t be able to unsee it. You’re welcome!

And don’t even get me started on the overflow menu—there’s still no API to change its behavior. Want to modify or disable it? Too bad, you can’t and it’s not designed that way. The only way to handle this is by replacing it entirely, assigning bar button items to pinnedTrailingGroup, and setting the representativeItem. You’d think Apple should have made this easier, but nope.

Oh, have you ever tried left-aligning a navigation bar title? It’s doable, but far from intuitive. One common trick is assigning a negated CGFloat.greatestFiniteMagnitude to push it to the left—not exactly an elegant solution. Using a custom titleView might seem like a better approach, but making it behave consistently across every view controller quickly becomes its own challenge–especially given that there’s no public API to determine which navigation bar appearance is currently in use (e.g., scrollEdgeAppearance or standardAppearance).

Faking a navigation bar with a UIView is often the only way to achieve the design you want. And that works—until you actually need navigation. The moment you introduce transitions, gestures, or system behaviors, everything falls apart. Suddenly, you find yourself attempting to reimplement UINavigationController. Don’t do that, seriously! It feels like Apple designed the navigation bar to be just customizable enough for basic styling but never actually considered developers who need more control than simply changing a color–even their own teams included.

Don’t get me wrong—things have improved over the years. The introduction of UINavigationBarAppearance in iOS 13 made customization much easier. Before that, it was often easier to iterate over the navigation bar’s private view hierarchy to tweak values, hoping it wouldn’t break in a future iOS update or get caught during App Store review. So yes, there’s progress, but it still feels too slow and not enough.

For example, there’s no way to access the content scroll view that the navigation controller uses to update the navigation bar appearance (unless it was previously set in setContentScrollView(_:)), forcing one to rely on obscure workarounds and tricks. This becomes especially problematic when trying to make a custom title view behave similarly to UIKit’s default implementation. And, of course, UIKit doesn’t provide an API to determine whether the navigation bar is currently displaying a large title or not. Yay!

Nearly two decades in, and we’re still stuck with a navigation bar that’s only somewhat customizable. As one of UIKit’s oldest and most prominent APIs, it should have received far more attention over the years to make it both more flexible and customizable—without forcing developers to rely on workarounds or opt out of system behavior entirely, only to then struggle to make the component behave like a system component again. It makes you wonder—will Apple ever do something about this, or do they genuinely think it’s fine as is?

And yes, it’s even worse in SwiftUI—but let’s not open that can of worms (for now).