5

Typically, translucent UINavigationBars have a light gray color above a white background.

Image 1

However, many navigation bars throughout iOS 11 have a white color. For example, navigation bars in the Files app are white AND translucent which are noticeably different from setting barTintColor to white.

Image 2 Image 3

How do I achieve this kind of effect on a UINavigationBar?

Affinity
  • 383
  • 1
  • 10

3 Answers3

1
  1. Set the barTintColor of the navigation bar to white.
  2. After that, subclass UINavigationBar and set the shadow image to an empty UIImage.

    class CustomNavBar: UINavigationBar {
       override func awakeFromNib() {
          super.awakeFromNib()
          shadowImage = UIImage()
       }
    }
    
  3. Finally, set the class of the navigation bar to the custom navigation bar class you just created.

Result

translucent navigation bar, white background

translucent navigation bar, partially black background

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
  • Thank you, but this seems to produce the same effect as setting `barTintColor` to white and removing the shadow without subclassing. The desired effect is more translucent and closer to `UIBlurEffectStyle.light` – Affinity Jan 10 '18 at 00:33
  • `navigationController?.navigationBar.isTranslucent = true` `navigationController?.navigationBar.shadowImage = UIImage()` `navigationController?.navigationBar.barTintColor = .white` This should give the effect as shown in your question. `UIBlurEffectStyle.light` gives a more dramatic effect, if that's what you want, see https://stackoverflow.com/questions/41781078/how-to-make-a-navigation-bar-and-status-bar-blurred-uiblureffect-ios-swift-3 – Jack Guo Jul 29 '18 at 21:42
0

I was trying to build an app with the same style as the Files app, but ended up with the same problem.

It seems that when barTintColor is non-nil, UINavigationBar will disable the translucency effect, and there doesn't seem to be a public way to force it back on.

I think there is a reasonable way to get this effect though: you could replace the background view of the navigation bar with your own UIVisualEffectView.

It's possible to suppress both the background view, and the separator view of a navigation bar by providing them with dummy UIImage objects.

navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()

You would need to create your own UINavigationBar subclass, suppress these two views, and then add your own UIVisualEffectView subview and position it to always sit at the bottom of the view stack.

It's not an amazing solution, but it should hopefully create the desired effect with minimal internal hacking of the navigation bar class.

TiM
  • 15,812
  • 4
  • 51
  • 79
0

So I've spent a huge amount of time researching this, and I've found the answer, but beware: it's very, very hacky.

So, the view that's responsible for creating this gray glow on the navigation bar is something called _UIVisualEffectBackdropView. More specifically, this view's layer has four filters attached (all of them are private CAFilters), and first of them, named luminanceCurveMap, creates this gray color for the navigation bar. So my solution is to

  1. Find _UIVisualEffectBackdropView in the view hierarchy of UINavigationBar
  2. Remove luminanceCurveMap filter of of its layer.

Here's the function I created to find _UIVisualEffectBackdropView in the hierarchy:

extension UIView {
    fileprivate static func findRecursively(typeName: String, in view: UIView) -> UIView? {
        let typeOf = type(of: view)
        if String(describing: typeOf) == typeName {
            return view
        } else {
            for subview in view.subviews {
                if let found = UIView.findRecursively(typeName: typeName, in: subview) {
                    return found
                }
            }
            return nil
        }
    }
}

And then override viewDidAppear in your custom UINavigationController subclass (only viewDidAppear, viewWillAppear for example didn't work):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if let backdrop = UIView.findRecursively(typeName: "_UIVisualEffectBackdropView", in: navigationBar) {
        let luminanceCurveMapIndex = backdrop.layer.filters?.firstIndex { filter in
            if let caFilter = filter as? NSObject, let name = caFilter.value(forKey: "name") as? String {
                return name == "luminanceCurveMap"
            } else {
                return false
            }
        }
        if let index = luminanceCurveMapIndex {
            backdrop.layer.filters?.remove(at: index)
        }
    }
}

I know it's a lot, but that's the I came out with. It preserves all native translucency behavior while giving me the look I needed.