28

It works with

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
    self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()


}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}

extension UIColor {
    func as1ptImage() -> UIImage {
        UIGraphicsBeginImageContext(CGSizeMake(1, 1))
        let ctx = UIGraphicsGetCurrentContext()
        self.setFill()
        CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

But when I add a UITableView it doesn't appear on it and when I add a UISearchView it appears but removes the navigation bar.

Anyone knows how to solve this?

TheoF
  • 795
  • 2
  • 7
  • 16

8 Answers8

46

You have to adjust the shadowImage property of the navigation bar.

Try this one. I created a category on UIColor as an helper, but you can refactor the way you prefer.

extension UIColor {
    func as1ptImage() -> UIImage {
        UIGraphicsBeginImageContext(CGSizeMake(1, 1))
        let ctx = UIGraphicsGetCurrentContext()
        self.setFill()
        CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

Option 1: on a single navigation bar

And then in your view controller (change the UIColor to what you like):

// We can use a 1px image with the color we want for the shadow image
self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()

// We need to replace the navigation bar's background image as well 
// in order to make the shadowImage appear. We use the same 1px color tecnique
self.navigationController?.navigationBar.setBackgroundImage(UIColor.yellowColor‌​().as1ptImage(), forBarMetrics: .Default)    

Option 2: using appearance proxy, on all navigation bars

Instead of setting the background image and shadow image on each navigation bar, it is possible to rely on UIAppearance proxy. You could try to add those lines to your AppDelegate, instead of adding the previous ones in the viewDidLoad.

// We can use a 1px image with the color we want for the shadow image
UINavigationBar.appearance().shadowImage = UIColor.redColor().as1ptImage()

// We need to replace the navigation bar's background image as well 
// in order to make the shadowImage appear. We use the same 1px color technique
UINavigationBar.appearance().setBackgroundImage(UIColor.yellowColor().as1ptImage(), forBarMetrics: .Default)
Alessandro Orrù
  • 3,443
  • 20
  • 24
  • Thanks, where do I put the extension UIColor? – TheoF Jul 13 '16 at 15:50
  • Wherever you prefer. If you have other UIColor extensions, you could put them together, or you could create a new file just for this extension, or you could make the extension private and use it alongside the UIViewController class. – Alessandro Orrù Jul 13 '16 at 15:52
  • I was asking because it shows an error saying "Declaration is only valid at file scope". – TheoF Jul 13 '16 at 16:01
  • yep, you can't put *inside* your class. Did it work? – Alessandro Orrù Jul 13 '16 at 16:02
  • I'm trying to add it on a UITableView and it doesn't work. I'm also trying to add it on a searchview, it works but removes the color of the navigationbar. Any idea of how to fix this? – TheoF Jul 14 '16 at 08:48
  • Can you provide some code? I don't know what you mean with trying to add to a table view or search view.. this should work on any view controller pushed on the navigation stack of the navigation controller – Alessandro Orrù Jul 14 '16 at 08:54
  • I used the code you provided above on my ViewController and it worked but then I added a UITableView and a UISearchView to the storyboard and it didn't appear on them. I tried to add the last two lines on classes I created for them, which worked for the UISearchView but made the navigationbar disappear but didn't work for the UITableView. – TheoF Jul 14 '16 at 09:03
  • I don't understand if you are adding the table view / search view as subviews to the view controller in which you implemented those lines, or if you are doing something different – Alessandro Orrù Jul 14 '16 at 09:05
  • Yes, both are subviews of the UIViewController that I used for the previous code. – TheoF Jul 14 '16 at 09:07
  • So, you have a UIViewController subclass (let's say MyViewController) and you added that lines to the `viewDidLoad` method, and all worked. Then you added a tableview and a search view in that view controller through the storyboard, **didn't change any code** and it stopped working? – Alessandro Orrù Jul 14 '16 at 09:08
  • No, it kept working on my UIViewController, worked on the search view but removed the navigation bar color and title and it didn't appear on the tableview. – TheoF Jul 14 '16 at 09:14
  • about the missing navigation bar color, it could be since we are replacing the background image, so maybe it becomes transparent. You should use this instead: `self.navigationController?.navigationBar.setBackgroundImage(UIColor.yellowColor().as1ptImage(), forBarMetrics: .Default)` using the color you want. Anyway, I still don't understand with "don't appear on the tableview". It appears on the navigation bar, not on a specific view. I really should see the code or some screenshots. – Alessandro Orrù Jul 14 '16 at 09:25
  • That line showed a message error "UIColor has no member blackColor", but it's strange that it removes the serchview navigation bar and not the UIView's one... I can send you the code but it's the same that you sent me. – TheoF Jul 14 '16 at 09:31
  • Have you written `UIColor.blackColor.as1ptImage()` or `UIColor.blackColor().as1ptImage()`? The last one is the correct one. I know that the lines of code are the same, but I think you are using them in the wrong place, that's why I asked for the code. Search view doesn't have any navigation bar. I think you are misunderstanding the usage of the navigation controller. – Alessandro Orrù Jul 14 '16 at 09:38
  • can you update the original post and add it? otherwise just give me a link where I can download it – Alessandro Orrù Jul 14 '16 at 09:41
  • This doesn't help, I should see the whole picture. Anyway, here is a link to a toy project where everything works, there is a view controller with a tableview and a search bar, I can't help you any better. https://dl.dropboxusercontent.com/u/8211801/test.zip – Alessandro Orrù Jul 14 '16 at 09:51
  • My mistake, I have a UIViewController, a UISearchView and a UITableView... That's 3 different screens, I understand why it wasn't clear now... Language mistake from my part. – TheoF Jul 14 '16 at 09:56
  • Yes I was imaging that, but this still doesn't make sense, since you **can't** push a UITableView in a navigation controller without wrapping it in a UIViewController (or using directly a UITableViewController subclass). – Alessandro Orrù Jul 14 '16 at 09:58
  • Can I send you the zip as you did so you have the whole code? – TheoF Jul 14 '16 at 09:59
  • sure, upload it somewhere and give me the link – Alessandro Orrù Jul 14 '16 at 09:59
  • ok I have seen your code. The main problem is that the "Charts" view controller in the storyboard is defined as a UITableViewController, but you inherit from a plain UIViewController in your code, so it doesn't work. Anyway, the best thing is to use directly the UIAppearance proxy. I updated my answer adding this other option, so you can use it instead of the old one. – Alessandro Orrù Jul 14 '16 at 10:41
24

Wonderful contributions from @TheoF, @Alessandro and @Pavel.

Here is what I did for...

Swift 4

extension UIColor {

    /// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
    ///
    /// - Returns: `self` as a 1x1 `UIImage`.
    func as1ptImage() -> UIImage {
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        setFill()
        UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
        let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
        UIGraphicsEndImageContext()
        return image
    }
}

Using it in viewDidLoad():

/* In this example, I have a ViewController embedded in a NavigationController in IB. */

// Remove the background color.
navigationController?.navigationBar.setBackgroundImage(UIColor.clear.as1ptImage(), for: .default)

// Set the shadow color.
navigationController?.navigationBar.shadowImage = UIColor.gray.as1ptImage()
backslash-f
  • 7,923
  • 7
  • 52
  • 80
5

Solution for Swift 4.0 - 5.2

Here is small extension for changing both Height and Color of bottom navbar line

extension UINavigationController
{
    func addCustomBottomLine(color:UIColor,height:Double)
    {
        //Hiding Default Line and Shadow
        navigationBar.setValue(true, forKey: "hidesShadow")
    
        //Creating New line
        let lineView = UIView(frame: CGRect(x: 0, y: 0, width:0, height: height))
        lineView.backgroundColor = color
        navigationBar.addSubview(lineView)
    
        lineView.translatesAutoresizingMaskIntoConstraints = false
        lineView.widthAnchor.constraint(equalTo: navigationBar.widthAnchor).isActive = true
        lineView.heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
        lineView.centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor).isActive = true
        lineView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
    }
}

And after adding this extension, you can call this method on any UINavigationController (e.g. from ViewController viewDidLoad())

self.navigationController?.addCustomBottomLine(color: UIColor.black, height: 20)
craft
  • 2,017
  • 1
  • 21
  • 30
Bios90
  • 801
  • 2
  • 14
  • 26
4

Putting @alessandro-orru's answer in one extension

extension UINavigationController {

    func setNavigationBarBorderColor(_ color:UIColor) {
        self.navigationBar.shadowImage = color.as1ptImage()
    }
}

extension UIColor {

    /// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
    ///
    /// - Returns: `self` as a 1x1 `UIImage`.
    func as1ptImage() -> UIImage {
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        setFill()
        UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
        let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
        UIGraphicsEndImageContext()
        return image
    }
}

then in your view controller just add:

self.navigationController?.setNavigationBarBorderColor(UIColor.red)
Claus
  • 5,662
  • 10
  • 77
  • 118
4

From iOS 13 on, you can use the UINavigationBarAppearance() class with the shadowColor property:

if #available(iOS 13.0, *) {
  let style = UINavigationBarAppearance()
  style.shadowColor = UIColor.clear // Effectively removes the border
  navigationController?.navigationBar.standardAppearance = style

  // Optional info for follow-ups:
  // The above will override other navigation bar properties so you may have to assign them here, for example:
  //style.buttonAppearance.normal.titleTextAttributes = [.font: UIFont(name: "YourFontName", size: 17)!]
  //style.backgroundColor = UIColor.orange
  //style.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white,
                               NSAttributedString.Key.font: UIFont(name: "AnotherFontName", size: 20.0)!]
} else {
  // Fallback on earlier versions
}
nontomatic
  • 2,003
  • 2
  • 24
  • 38
2

For iOS 13 and later

    guard let navigationBar = navigationController?.navigationBar else { return }
    navigationBar.isTranslucent = true
    if #available(iOS 13.0, *) {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithTransparentBackground()
        appearance.backgroundImage = UIImage()
        appearance.backgroundColor = .clear
        navigationBar.standardAppearance = appearance
    } else {
        navigationBar.setBackgroundImage(UIImage(), for: .default)
        navigationBar.shadowImage = UIImage()
    }
Muhammad Aamir Ali
  • 20,419
  • 10
  • 66
  • 57
1

for Swift 3.0 just change this line:

CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))

to this:

ctx?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
Pavel
  • 61
  • 7
1

There's a much better option available these days:

UINavigationBar.appearance().shadowImage = UIImage()
MartijndeM
  • 146
  • 1
  • 9