Search code examples
iosuinavigationbaruiappearance

UINavigationBar Custom Color Hairline Border


First of all, I did search this before posting this question but if there is an answer out there, it is buried under the millions of questions about how to remove the default bottom border of a navigation bar.

I do NOT want to remove the bottom border ("shadow") of the navigation bar.


I am trying to "theme" my app by the usual method of using appearance proxies. I can globaly change most visual attributes of UINavigationBar with code like the following:

let navigationBarProxy = UINavigationBar.appearance()
navigationBarProxy.isTranslucent = false
navigationBarProxy.barTintColor = myBarBackgroundColor
navigationBarProxy.tintColor = myBarTextColor

Regarding the 'hairline' bottom border of the bar (or as it is known, the "shadow"), I can either set the default one by doing nothing or specifying nil:

navigationBarProxy.shadowImage = nil

...or I can specify a custom color by assigning a solid image of the color I'm after:

navigationBarProxy.shadowImage = UIImage.withColor(myBorderColor)

(uses helper extension:)

extension UIImage {
    public static func withColor(_ color: UIColor?, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage? {
        let rect = CGRect(origin: CGPoint.zero, size: size)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        let actualColor = color ?? .clear
        context?.setFillColor(actualColor.cgColor)
        context?.fill(rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }
}

However, the approach above gives me (on retina devices) a 1pt, 2px border, whereas the default, light gray one is actually 0.5pt, 1px (a.k.a. "hairline").

Is there any way to achieve a 0.5 pt (1px), custom-colored bottom border (shadow) for UINavigationBar?


I guess I could use a runtime generated, background image that is for the most part solid, but has a 1px border of my choice color "baked in" at the bottom. But this seems inelegant at best, and I'm not sure how it would work when that navigation bar height changes: is the image sliced, or simply stretched, or what?


Solution

  • Based on the chosen answer found here (with small changes because it was old):

    How to change the border color below the navigation bar?

    // in viewDidLoad
    UIView * navBorder = [[UIView alloc] initWithFrame:CGRectMake(0,
                                                                  self.navigationController.navigationBar.frame.size.height, // <-- same height, not - 1
                                                                  self.navigationController.navigationBar.frame.size.width,
                                                                  1/[UIScreen mainScreen].scale)]; // <-- 5/5S/SE/6 will be 0.5, 6+/X will be 0.33
    // custom color here
    [navBorder setBackgroundColor:customColor];
    
    [self.navigationController.navigationBar addSubview:navBorder];
    


    Credit here for finding scale programmatically: Get device image scale (e.g. @1x, @2x and @3x)

    *NOTE:
    iPhone 6+/X are x3, so 1px height will be 0.33pt
    iPhone 5/5S/SE/6 are x2, so 1px height will be 0.5pt
    Tested in simulator, may need to verify on actual devices.

    Visually same as default nav bar with a custom color.