For iOS 13:

Use the .shadowColor property

If this property is nil or contains the clear color, the bar displays no shadow

For instance:

let navigationBar = navigationController?.navigationBar
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.shadowColor = .clear
navigationBar?.scrollEdgeAppearance = navigationBarAppearance

For iOS 12 and below:

To do this, you should set a custom shadow image. But for the shadow image to be shown you also need to set a custom background image, quote from Apple’s documentation:

For a custom shadow image to be shown, a custom background image must
also be set with the setBackgroundImage(_:for:) method. If the default
background image is used, then the default shadow image will be used
regardless of the value of this property.

So:

let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(#imageLiteral(resourceName: "BarBackground"),
                                                        for: .default)
navigationBar.shadowImage = UIImage()

Above is the only “official” way to hide it. Unfortunately, it removes bar’s translucency.

I don’t want background image, just color##

You have those options:

  1. Solid color, no translucency:

     navigationBar.barTintColor = UIColor.redColor()
     navigationBar.isTranslucent = false
     navigationBar.setBackgroundImage(UIImage(), for: .default)
     navigationBar.shadowImage = UIImage()
    
  2. Create small background image filled with color and use it.

  3. Use ‘hacky’ method described below. It will also keep bar translucent.

How to keep bar translucent?##

To keep translucency you need another approach, it looks like a hack but works well. The shadow we’re trying to remove is a hairline UIImageView somewhere under UINavigationBar. We can find it and hide/show it when needed.

Instructions below assume you need hairline hidden only in one controller of your UINavigationController hierarchy.

  1. Declare instance variable:

    private var shadowImageView: UIImageView?
    
  2. Add method which finds this shadow (hairline) UIImageView:

    private func findShadowImage(under view: UIView) -> UIImageView? {
        if view is UIImageView && view.bounds.size.height <= 1 {
            return (view as! UIImageView)
        }
    
        for subview in view.subviews {
            if let imageView = findShadowImage(under: subview) {
                return imageView
            }
        }
        return nil
    }
    
  3. Add/edit viewWillAppear/viewWillDisappear methods:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        if shadowImageView == nil {
            shadowImageView = findShadowImage(under: navigationController!.navigationBar)
        }
        shadowImageView?.isHidden = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
    
        shadowImageView?.isHidden = false
    }
    

The same method should also work for UISearchBar hairline,
and (almost) anything else you need to hide 🙂

Many thanks to @Leo Natan for the original idea!