How to remove and add back navigationBar shadow? - ios

if I remove navigationBar shadow:
self.navigationController?.navigationBar.shadowImage = UIImage()
how I can add back this shadow?

From the docs.
The default value is nil, which corresponds to the default shadow
image.
So it should be enough that you set
self.navigationController?.navigationBar.shadowImage = nil

//Extension
extension UINavigationBar {
func shouldRemoveShadow(_ value: Bool) -> Void {
if value {
self.setValue(true, forKey: "hidesShadow")
} else {
self.setValue(false, forKey: "hidesShadow")
}
}
}
//Use in view controller.
self.navigationController?.navigationBar.shouldRemoveShadow(true)

Related

Animate navigation bar barTintColor change in iOS10 not working

I upgraded to XCode 8.0 / iOS 10 and now the color change animation of my navigation bar is not working anymore, it changes the color directly without any animation.
UIView.animateWithDuration(0.2, animations: {
self.navigationController?.navigationBar.barTintColor = currentSection.color!
})
Anyone knows how to fix this?
To animate navigationBar’s color change in iOS10 you need to call layoutIfNeeded after setting color inside animation block.
Example code:
UIView.animateWithDuration(0.5) {
self.navigationController?.navigationBar.barTintColor = UIColor.redColor()
self.navigationController?.navigationBar.layoutIfNeeded()
}
Also I want to inform that Apple doesn’t officialy support animations in such properties like barTintColor, so that method can break at any time.
If you call -layoutIfNeeded on the navigation bar during the animation
block it should update its background properties, but given the nature
of what these properties do, there really hasn't ever been any kind of
guarantee that you could animate any of them.
Interactive animation
Define a protocol:
/// Navigation bar colors for `ColorableNavigationController`, called on `push` & `pop` actions
public protocol NavigationBarColorable: UIViewController {
var navigationTintColor: UIColor? { get }
var navigationBarTintColor: UIColor? { get }
}
public extension NavigationBarColorable {
var navigationTintColor: UIColor? { return nil }
}
Define a custom NavigationController subclass:
class AppNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
navigationBar.shadowImage = UIImage()
if let colors = rootViewController as? NavigationBarColorable {
setNavigationBarColors(colors)
}
}
private var previousViewController: UIViewController? {
guard viewControllers.count > 1 else {
return nil
}
return viewControllers[viewControllers.count - 2]
}
override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
if let colors = viewController as? NavigationBarColorable {
setNavigationBarColors(colors)
}
super.pushViewController(viewController, animated: animated)
}
override open func popViewController(animated: Bool) -> UIViewController? {
if let colors = previousViewController as? NavigationBarColorable {
setNavigationBarColors(colors)
}
// Let's start pop action or we can't get transitionCoordinator()
let popViewController = super.popViewController(animated: animated)
// Secure situation if user cancelled transition
transitionCoordinator?.animate(alongsideTransition: nil, completion: { [weak self] context in
guard let `self` = self else { return }
guard let colors = self.topViewController as? NavigationBarColorable else { return }
self.setNavigationBarColors(colors)
})
return popViewController
}
override func popToRootViewController(animated: Bool) -> [UIViewController]? {
if let colors = rootViewController as? NavigationBarColorable {
setNavigationBarColors(colors)
}
let controllers = super.popToRootViewController(animated: animated)
return controllers
}
private func setNavigationBarColors(_ colors: NavigationBarColorable) {
if let tintColor = colors.navigationTintColor {
navigationBar.titleTextAttributes = [
.foregroundColor : tintColor
]
navigationBar.tintColor = tintColor
}
navigationBar.barTintColor = colors.navigationBarTintColor
}
}
Now you can conform to NavigationBarColorable in any controller inside the AppNavigationController and give it any color you want.
extension FirstViewController: NavigationBarColorable {
public var navigationBarTintColor: UIColor? { UIColor.red }
public var navigationTintColor: UIColor? { UIColor.white }
}
extension SecondViewController: NavigationBarColorable {
public var navigationBarTintColor: UIColor? { UIColor.blue }
public var navigationTintColor: UIColor? { UIColor.orange }
}
Don't forget to implement this useful extension:
extension UINavigationController {
var rootViewController: UIViewController? {
return viewControllers.first
}
}

Transparent UINavigationBar in Swift

I am trying to make my UINavigationBar in UINavigationController transparent. I created a subclass of UINavigationController and liked it to a scene in my storyboard file. Here's a piece of my subclass:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let size = self.navigationBar.frame.size
self.navigationBar.setBackgroundImage(imageWithColor(UIColor.blackColor(), size: size, alpha: 0.2), forBarMetrics: UIBarMetrics.Default)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func imageWithColor(color: UIColor, size: CGSize, alpha: CGFloat) -> UIImage {
UIGraphicsBeginImageContext(size)
let currentContext = UIGraphicsGetCurrentContext()
let fillRect = CGRectMake(0, 0, size.width, size.height)
CGContextSetFillColorWithColor(currentContext, color.CGColor)
CGContextSetAlpha(currentContext, alpha)
CGContextFillRect(currentContext, fillRect)
let retval: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return retval
}
When I run my application a have a navigation bar transparent, but status bar is just black.
For example if I do such thing on UITabBar - it works.
Hope it help you
Swift 2:
self.navigationController.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController.navigationBar.shadowImage = UIImage()
self.navigationController.navigationBar.isTranslucent = true
self.navigationController.view.backgroundColor = UIColor.clearColor()
Swift 4.2 to Swift 5.1
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.view.backgroundColor = UIColor.clear
Or If you want to sublcass the navigation controller then refer this answer.
Change the status bar style via :
In your Info.plist you need to define View controller-based status bar appearance to any value.
UIApplication.shared.statusBarStyle = .lightContent
If you want to hide the status bar:
UIApplication.shared.isStatusBarHidden = true
Getting this output by light content and by transparent navigation. I have view background is gray. you can see the transparency.
iPhone XR - Swift 4.2 - Large Titles (Test Screenshot)
If you're using Swift 2.0 uses the code block below:
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.translucent = true
For Swift 3.0 use:
navigationController?.setNavigationBarHidden(false, animated: true)
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = true
Swift 3.0.1 with Xcode 8.1
In your UINavigationController
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationBar.shadowImage = UIImage()
self.navigationBar.isTranslucent = true
self.view.backgroundColor = UIColor.clear
}
Xcode 8.x : Swift 3: Extension for the same
Write once use throughout
extension UINavigationBar {
func transparentNavigationBar() {
self.setBackgroundImage(UIImage(), for: .default)
self.shadowImage = UIImage()
self.isTranslucent = true
}
}
Create an extension of UINavigationController and present or hide transparent navigation bar.
extension UINavigationController {
public func presentTransparentNavigationBar() {
navigationBar.setBackgroundImage(UIImage(), for:UIBarMetrics.default)
navigationBar.isTranslucent = true
navigationBar.shadowImage = UIImage()
setNavigationBarHidden(false, animated:true)
}
public func hideTransparentNavigationBar() {
setNavigationBarHidden(true, animated:false)
navigationBar.setBackgroundImage(UINavigationBar.appearance().backgroundImage(for: UIBarMetrics.default), for:UIBarMetrics.default)
navigationBar.isTranslucent = UINavigationBar.appearance().isTranslucent
navigationBar.shadowImage = UINavigationBar.appearance().shadowImage
}
}
I tried all methods above and still got white space instead of content that supposed to be rendered through. If you want to draw regular subview (Google map f.e.), not UIScrollView content through navigation bar, then you need to set subview's frame in viewDidAppear.
So step 1:
existingNavigationBar.setBackgroundImage(transparentImageFromAssets, for: .default)
existingNavigationBar.shadowImage = transparentImageFromAssets
existingNavigationBar.isTranslucent = true
step 2:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.mapView.frame = UIScreen.main.bounds
}
This worked for me.

hidesBarsOnTap - Navigation Bar hidden/displayed event?

I would like to set the background color of a view to black when the navigation bar is hidden, and to white when the navigation bar is displayed.
The property hidesBarsOnTap is set to true in viewDidLoad. This works fine:
navigationController?.hidesBarsOnTap = true
How can I be notified when the bars are hidden and displayed?
Sorry, I made a mistake. The following code does exactly what you want. If you have a toolbar, you can set it to hide as well.
class ViewController: UIViewController {
var hidden = false {
didSet {
if let nav = navigationController {
nav.setNavigationBarHidden(hidden, animated: true)
nav.setToolbarHidden(hidden, animated: true)
view.backgroundColor = hidden ? UIColor.blackColor() : UIColor.whiteColor()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
let recognizer = UITapGestureRecognizer(target: self, action: "tap:")
view.addGestureRecognizer(recognizer)
}
func tap(recognizer: UITapGestureRecognizer) {
if recognizer.state == .Ended {
hidden = !hidden
}
}
}
since hidesBarsOnTap is of type boolean, we can easily use it to check and use it as option like in below example:
var set : Bool = navigationController?.hidesBarsOnTap //true or false
if (set){
//do what you want when set
}else{
//do what you want when it is not set
}

How to remove border of the navigationBar in swift?

i've been trying to remove the navigationBars border without luck. I've researched and people seem to tell to set shadowImage and BackgroundImage to nil, but this does not work in my case.
My code
self.navigationController?.navigationBar.barTintColor = UIColor(rgba: "#4a5866")
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: ""), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage(named: "")
illustration:
The trouble is with these two lines:
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: ""), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage(named: "")
Since you don't have an image with no name, UIImage(named: "") returns nil, which means the default behavior kicks in:
When non-nil, a custom shadow image to show instead of the default shadow image. For a custom shadow to be shown, a custom background image must also be set with -setBackgroundImage:forBarMetrics: (if the default background image is used, the default shadow image will be used).
You need a truly empty image, so just initialize with UIImage():
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
Swift 4 & Swift 5
Removing border:
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for:.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.layoutIfNeeded()
Restoring border:
self.navigationController?.navigationBar.setBackgroundImage(nil, for:.default)
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.layoutIfNeeded()
With Swift 2 you can do it this way:
AppDelegate file
Inside func application(..., didFinishLaunchingWithOptions launchOptions:...)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().setBackgroundImage(UIImage(), forBarMetrics: .Default)
for Swift 3:
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
Just write this in the extension of UINavigationBar
extension UINavigationBar {
func shouldRemoveShadow(_ value: Bool) -> Void {
if value {
self.setValue(true, forKey: "hidesShadow")
} else {
self.setValue(false, forKey: "hidesShadow")
}
}
}
And in your viewController...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.shouldRemoveShadow(true)
}
And to get this undone for any viewController, just pass false.
this will remove the shadow image altogether
for parent in self.navigationController!.navigationBar.subviews {
for childView in parent.subviews {
if(childView is UIImageView) {
childView.removeFromSuperview()
}
}
}
Swift 5
When using setBackgroundImage / shadowImage to hide the hairline, there's a slight delay. This method removes the delay. Credit to Chameleon Framework. This is the method they use (in ObjC)
extension UINavigationController {
func hideHairline() {
if let hairline = findHairlineImageViewUnder(navigationBar) {
hairline.isHidden = true
}
}
func restoreHairline() {
if let hairline = findHairlineImageViewUnder(navigationBar) {
hairline.isHidden = false
}
}
func findHairlineImageViewUnder(_ view: UIView) -> UIImageView? {
if view is UIImageView && view.bounds.size.height <= 1.0 {
return view as? UIImageView
}
for subview in view.subviews {
if let imageView = self.findHairlineImageViewUnder(subview) {
return imageView
}
}
return nil
}
}
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithTransparentBackground()
Set barStyle to .Black before setting the tint:
self.navigationController?.navigationBar.translucent = false
self.navigationController?.navigationBar.barStyle = .Black
self.navigationController?.navigationBar.barTintColor = UIColor.blueColor()
For iOS 13+:
let appearance = UINavigationBarAppearance()
appearance.shadowColor = .clear
Assign this appearance to the UINavigationBar:
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.compactAppearance = appearance
Setting shadowImage = UIImage() didn't work for me.
Luca Davanzo's answer is great, but it does not work in iOS 10. I altered it to work in iOS 10 and below.
for parent in navigationController!.view.subviews {
for child in parent.subviews {
for view in child.subviews {
if view is UIImageView && view.frame.height == 0.5 {
view.alpha = 0
}
}
}
}
You can also extend UINavigationController and call this off of that. removeFromSuperview() on the line will not work on iOS 10, so I just set the alpha to 0 so this one call is compatible everywhere.
To remove border from UINavigationBar in Swift 3+, use:
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
UINavigationBar.appearance().isTranslucent = false
Swiftier method of Jack Chen:
extension UINavigationController {
var isHiddenHairline: Bool {
get {
guard let hairline = findHairlineImageViewUnder(navigationBar) else { return true }
return hairline.isHidden
}
set {
if let hairline = findHairlineImageViewUnder(navigationBar) {
hairline.isHidden = newValue
}
}
}
private func findHairlineImageViewUnder(_ view: UIView) -> UIImageView? {
if view is UIImageView && view.bounds.size.height <= 1.0 {
return view as? UIImageView
}
for subview in view.subviews {
if let imageView = self.findHairlineImageViewUnder(subview) {
return imageView
}
}
return nil
}
}
Using:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isHiddenHairline = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.isHiddenHairline = false
}
Only this worked for me,
self.navigationController?.navigationBar.shadowImage = UIImage()
Ref
for swift 3
in viewDidLoad method
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
Updated for Swift 4 in case someone is wondering
navigationBar.shadowImage = UIImage()
navigationBar.backIndicatorImage = UIImage()
It's even less verbose now.
The accepted answer worked for me but I noticed when I wanted the shadow image to reappear when popping back or pushing forward to another vc there was a noticeable blink in the navigation bar.
Using this method navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
in viewWillAppear the shadow bar is hidden in the current visible view controller.
Using these 2 methods
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
in viewWillDisappear the blink still happens but only when the shadow image reappears and not the navigation bar itself.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 1. hide the shadow image in the current view controller you want it hidden in
navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// 2. show the shadow image when pushing or popping in the next view controller. Only the shadow image will blink
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
}
If you want to remove only the bottom line and keep the solid color of navigationBar, add these lines of code in viewDidLoad:
Swift 3, 4:
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = false
Peace!
This is the way if you want to do it without changing the background color:
// Remove the border ImageView from the NavigationBar background
func hideBottomBorder() {
for view in navigationBar.subviews.filter({ NSStringFromClass($0.dynamicType) == "_UINavigationBarBackground" }) as [UIView] {
if let imageView = view.subviews.filter({ $0 is UIImageView }).first as? UIImageView {
imageView.removeFromSuperview()
}
}
}
NOTE:
This might crash on a production app. Apparently the NavigationBar doesn't like its view disappearing
Within AppDelegate, this has globally changed the format of the NavBar and removes the bottom line/border:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
UINavigationBar.appearance().setBackgroundImage(UIImage(), forBarPosition: UIBarPosition.Any, barMetrics: UIBarMetrics.Default)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
UINavigationBar.appearance().barTintColor = UIColor.redColor()
UINavigationBar.appearance().translucent = false
UINavigationBar.appearance().clipsToBounds = false
//UINavigationBar.appearance().backgroundColor = UIColor.redColor()
UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName : (UIFont(name: "FONT NAME", size: 18))!, NSForegroundColorAttributeName: UIColor.whiteColor()] }
Haven't managed to implement anything different on a specific VC, but this will help 90% of people
in your custom navigationController add these lines:
self.navigationBar.setBackgroundImage(UIImage(), for:.default)
self.navigationBar.shadowImage = UIImage()
self.navigationBar.layoutIfNeeded()
Important Note
the last line is important if you use the first line viewDidLoad() method because navigationController should redraw nav bar but easily you can use this without layoutIfNeeded() in the viewWillAppear() method before it draws the nav bar
I use this code in AppDelegate's didFinishLaunchingWithOptions method to reach it in whole app:
let barAppearance = UINavigationBar.appearance()
if #available(iOS 13, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
barAppearance.standardAppearance = appearance
barAppearance.scrollEdgeAppearance = appearance
} else {
barAppearance.setBackgroundImage(UIImage(), for: UIBarPosition.any, barMetrics: UIBarMetrics.defaultPrompt)
barAppearance.shadowImage = UIImage()
}
for the swift3 you should write slightly different way:
self.navigationController?.navigationBar.setBackgroundImage(UIImage(),
for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
This is a streamlined version of Gaurav Chandarana's answer.
extension UINavigationBar {
func hideShadow(_ value: Bool = true) {
setValue(value, forKey: "hidesShadow")
}
}
App delegate
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: UIBarMetrics.default)
UINavigationBar.appearance().shadowImage = UIImage()
The border line is an UIImageView and removing a subview which is an imageView will remove barButtonItems with UIImageView. Below code will help you remove it. Hope this helps someone who faced an issue like me.
for parent in self.navigationController!.navigationBar.subviews {
for childView in parent.subviews {
if childView.frame.height == 0.5 {
childView.removeFromSuperview()
}
}
}
The border UIImageView is only 0.5 in height so this code removes only that.
this is the answer in swift 3 base of Nate Cook answer
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
iOS 11 and Swift 4 You should try following if you want to remove the border but don't to make the navigitonbar translucent
self.navigationBar.shadowImage = UIImage()
I am using Xcode 14.2. If you want to do this using Xcode UI, then select the Navigation controller, then Navigation Bar, then Attributes Inspector and Under Scroll Edge Appearance, set the Shadow Color to clear color.
I found changing the navigation bar shadow color to Clear from the activity inspector to solve this issue
[changing the navigation bar shadow color****strong text][1]
[1]: https://i.stack.imgur.com/i3Kdo.png***strong text***

How to hide UINavigationBar 1px bottom line

I have an app that sometimes needs its navigation bar to blend in with the content.
Does anyone know how to get rid of or to change color of this annoying little bar?
On the image below situation i have - i'm talking about this 1px height line below "Root View Controller"
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:
Solid color, no translucency:
navigationBar.barTintColor = UIColor.redColor()
navigationBar.isTranslucent = false
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
Create small background image filled with color and use it.
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.
Declare instance variable:
private var shadowImageView: UIImageView?
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
}
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!
Here is the hack. Since it works on key paths might break in the future. But for now it works as expected.
Swift:
self.navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
Objective C:
[self.navigationController.navigationBar setValue:#(YES) forKeyPath:#"hidesShadow"];
If you just want to use a solid navigation bar color and have set this up in your storyboard, use this code in your AppDelegate class to remove the 1 pixel border via the appearance proxy:
[[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init]
forBarPosition:UIBarPositionAny
barMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
Try this:
[[UINavigationBar appearance] setBackgroundImage: [UIImage new]
forBarMetrics: UIBarMetricsDefault];
[UINavigationBar appearance].shadowImage = [UIImage new];
Below image has the explanation (iOS7 NavigationBar):
And check this SO question:
iOS7 - Change UINavigationBar border color
The swift way to do it:
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
UINavigationBar.appearance().shadowImage = UIImage()
Wanted to add the Swift version of Serhii's answer. I created a UIBarExtension.swift with the following:
import Foundation
import UIKit
extension UINavigationBar {
func hideBottomHairline() {
self.hairlineImageView?.isHidden = true
}
func showBottomHairline() {
self.hairlineImageView?.isHidden = false
}
}
extension UIToolbar {
func hideBottomHairline() {
self.hairlineImageView?.isHidden = true
}
func showBottomHairline() {
self.hairlineImageView?.isHidden = false
}
}
extension UIView {
fileprivate var hairlineImageView: UIImageView? {
return hairlineImageView(in: self)
}
fileprivate func hairlineImageView(in view: UIView) -> UIImageView? {
if let imageView = view as? UIImageView, imageView.bounds.height <= 1.0 {
return imageView
}
for subview in view.subviews {
if let imageView = self.hairlineImageView(in: subview) { return imageView }
}
return nil
}
}
Simple solution in swift
let navigationBar = self.navigationController?.navigationBar
navigationBar?.setBackgroundImage(UIImage(), forBarPosition: UIBarPosition.Any, barMetrics: UIBarMetrics.Default)
navigationBar?.shadowImage = UIImage()
As of iOS 13 there is a system API to set or remove the shadow
UIKit uses shadowImage and the shadowColor property to determine the shadow's
appearance. When shadowImage is nil, the bar displays a default shadow tinted
according to the value in the shadowColor property. If shadowColor is nil or
contains the clearColor color, the bar displays no shadow.
let appearance = UINavigationBarAppearance()
appearance.shadowImage = nil
appearance.shadowColor = nil
navigationController.navigationBar.standardAppearance = appearance
https://developer.apple.com/documentation/uikit/uibarappearance/3198009-shadowimage
Can also be hidden from Storyboard (working on Xcode 10.1)
By adding runtime attribute: hidesShadow - Boolean - True
In Swift 3.0
Edit your AppDelegate.swift by adding the following code to your application function:
// Override point for customization after application launch.
// Remove border in navigationBar
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
After studying the answer from Serhil, I created a pod UINavigationBar+Addition that can easily hide the hairline.
#import "UINavigationBar+Addition.h"
- (void)viewDidLoad {
[super viewDidLoad];
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar hideBottomHairline];
}
Swift 4
//for hiding navigation bar shadow line
navigationController?.navigationBar.shadowImage = UIImage()
pxpgraphics' solution updated for Swift 2.0
extension UINavigationBar {
func hideBottomHairline()
{
hairlineImageViewInNavigationBar(self)?.hidden = true
}
func showBottomHairline()
{
hairlineImageViewInNavigationBar(self)?.hidden = false
}
private func hairlineImageViewInNavigationBar(view: UIView) -> UIImageView?
{
if let imageView = view as? UIImageView where imageView.bounds.height <= 1
{
return imageView
}
for subview: UIView in view.subviews
{
if let imageView = hairlineImageViewInNavigationBar(subview)
{
return imageView
}
}
return nil
}
}
extension UIToolbar
{
func hideHairline()
{
let navigationBarImageView = hairlineImageViewInToolbar(self)?.hidden = true
}
func showHairline()
{
let navigationBarImageView = hairlineImageViewInToolbar(self)?.hidden = false
}
private func hairlineImageViewInToolbar(view: UIView) -> UIImageView?
{
if let imageView = view as? UIImageView where imageView.bounds.height <= 1
{
return imageView
}
for subview: UIView in view.subviews
{
if let imageView = hairlineImageViewInToolbar(subview)
{
return imageView
}
}
return nil
}
}
I use a UINavigationBar extension that enables me to hide/show that shadow using the UIAppearance API or selecting which navigation bar has to hide/show that shadow using Storyboard (or source code). Here is the extension:
import UIKit
private var flatAssociatedObjectKey: UInt8 = 0
/*
An extension that adds a "flat" field to UINavigationBar. This flag, when
enabled, removes the shadow under the navigation bar.
*/
#IBDesignable extension UINavigationBar {
#IBInspectable var flat: Bool {
get {
guard let obj = objc_getAssociatedObject(self, &flatAssociatedObjectKey) as? NSNumber else {
return false
}
return obj.boolValue;
}
set {
if (newValue) {
let void = UIImage()
setBackgroundImage(void, forBarPosition: .Any, barMetrics: .Default)
shadowImage = void
} else {
setBackgroundImage(nil, forBarPosition: .Any, barMetrics: .Default)
shadowImage = nil
}
objc_setAssociatedObject(self, &flatAssociatedObjectKey, NSNumber(bool: newValue),
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Now, to disable the shadow across all navigation bars you have to use:
UINavigationBar.appearance().flat = true
Or you can enable/disable this behavior using storyboards:
Swift 4 Tested
ONE LINE SOLUTION
In Viewdidload()
Set Navigation controller's userdefault value true for key "hidesShadow"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
}
For iOS 13+
The trick is to initialize 'UINavigationBarAppearance' with TransparentBackground. Then you could easily remove the horizontal line of the navigation bar.
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = .green // Required background color
Finally, add the appearance changes to the navigation item as the apple suggested.
self.navigationItem.standardAppearance = appearance
self.navigationItem.scrollEdgeAppearance = appearance
self.navigationItem.compactAppearance = appearance
Swift put this
UINavigationBar.appearance().setBackgroundImage(UIImage(), forBarPosition: .Any, barMetrics: .Default)
UINavigationBar.appearance().shadowImage = UIImage()
in
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
Another option if you want to preserve translucency and you don't want to subclass every UINavigationController in your app:
#import <objc/runtime.h>
#implementation UINavigationController (NoShadow)
+ (void)load {
Method original = class_getInstanceMethod(self, #selector(viewWillAppear:));
Method swizzled = class_getInstanceMethod(self, #selector(swizzled_viewWillAppear:));
method_exchangeImplementations(original, swizzled);
}
+ (UIImageView *)findHairlineImageViewUnder:(UIView *)view {
if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
return (UIImageView *)view;
}
for (UIView *subview in view.subviews) {
UIImageView *imageView = [self findHairlineImageViewUnder:subview];
if (imageView) {
return imageView;
}
}
return nil;
}
- (void)swizzled_viewWillAppear:(BOOL)animated {
UIImageView *shadow = [UINavigationController findHairlineImageViewUnder:self.navigationBar];
shadow.hidden = YES;
[self swizzled_viewWillAppear:animated];
}
#end
Slightly Swift Solution
func setGlobalAppearanceCharacteristics () {
let navigationBarAppearace = UINavigationBar.appearance()
navigationBarAppearace.tintColor = UIColor.white
navigationBarAppearace.barTintColor = UIColor.blue
navigationBarAppearace.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBarAppearace.shadowImage = UIImage()
}
Two lines solution that works for me. Try to add this in ViewDidLoad method:
navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
self.extendedLayoutIncludesOpaqueBars = true
Here's a very simple solution:
self.navigationController.navigationBar.clipsToBounds = YES;
In iOS8, if you set the UINavigationBar.barStyle to .Black you can set the bar's background as plain color without the border.
In Swift:
UINavigationBar.appearance().translucent = false
UINavigationBar.appearance().barStyle = UIBarStyle.Black
UINavigationBar.appearance().barTintColor = UIColor.redColor()
Solution in Swift 4.2:
private func removeHairlineFromNavbar() {
UINavigationBar.appearance().setBackgroundImage(
UIImage(),
for: .any,
barMetrics: .default)
UINavigationBar.appearance().shadowImage = UIImage()
}
Just put this function at the first Viewcontroller and call it in viewdidload
Simple solution – Swift 5
Create an extension:
extension UIImage {
class func hideNavBarLine(color: UIColor) -> UIImage? {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(color.cgColor)
context?.fill(rect)
let navBarLine = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return navBarLine
}
}
Add this to viewDidLoad():
self.navigationController?.navigationBar.shadowImage = UIImage.hideNavBarLine(color: UIColor.clear)
For iOS 9 users, this worked for me. just add this:
UINavigationBar.appearance().shadowImage = UIImage()
The problem with setting a background image is it removes blurring. You can remove it without setting a background image. See my answer here.
pxpgraphics's answer for Swift 3.0.
import Foundation
import UIKit
extension UINavigationBar {
func hideBottomHairline() {
let navigationBarImageView = hairlineImageViewInNavigationBar(view: self)
navigationBarImageView!.isHidden = true
}
func showBottomHairline() {
let navigationBarImageView = hairlineImageViewInNavigationBar(view: self)
navigationBarImageView!.isHidden = false
}
private func hairlineImageViewInNavigationBar(view: UIView) -> UIImageView? {
if view is UIImageView && view.bounds.height <= 1.0 {
return (view as! UIImageView)
}
let subviews = (view.subviews as [UIView])
for subview: UIView in subviews {
if let imageView: UIImageView = hairlineImageViewInNavigationBar(view: subview) {
return imageView
}
}
return nil
}
}
extension UIToolbar {
func hideHairline() {
let navigationBarImageView = hairlineImageViewInToolbar(view: self)
navigationBarImageView!.isHidden = true
}
func showHairline() {
let navigationBarImageView = hairlineImageViewInToolbar(view: self)
navigationBarImageView!.isHidden = false
}
private func hairlineImageViewInToolbar(view: UIView) -> UIImageView? {
if view is UIImageView && view.bounds.height <= 1.0 {
return (view as! UIImageView)
}
let subviews = (view.subviews as [UIView])
for subview: UIView in subviews {
if let imageView: UIImageView = hairlineImageViewInToolbar(view: subview) {
return imageView
}
}
return nil
}
}
You should add a view to a bottom of the UISearchBar
let rect = searchController.searchBar.frame;
let lineView : UIView = UIView.init(frame: CGRect.init(x: 0, y: rect.size.height-1, width: rect.size.width, height: 1))
lineView.backgroundColor = UIColor.init(hexString: "8CC73E")
searchController.searchBar.addSubview(lineView)
I Just created an extension for this... Sorry about formatting (this is my first answer).
Usage:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.hideShadow = true
}
Extension:
UINavigationController.swift
// Created by Ricardo López Rey on 16/7/15.
import Foundation
struct UINavigationControllerExtension {
static var hideShadowKey : String = "HideShadow"
static let backColor = UIColor(red: 247/255, green: 247/255, blue: 248/255, alpha: 1.0)
}
extension UINavigationController {
var hideShadow : Bool {
get {
if let ret = objc_getAssociatedObject(self, &UINavigationControllerExtension.hideShadowKey) as? Bool {
return ret
} else {
return false
}
}
set {
objc_setAssociatedObject(self,&UINavigationControllerExtension.hideShadowKey,newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
if newValue {
self.navigationBar.setBackgroundImage(solidImage(UINavigationControllerExtension.backColor), forBarMetrics: UIBarMetrics.Default)
self.navigationBar.shadowImage = solidImage(UIColor.clearColor())
} else {
self.navigationBar.setBackgroundImage(nil, forBarMetrics: UIBarMetrics.Default)
}
}
}
private func solidImage(color: UIColor, size: CGSize = CGSize(width: 1,height: 1)) -> UIImage {
var rect = CGRectMake(0, 0, size.width, size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
var image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Within AppDelegate, this has globally changed the format of the NavBar:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
UINavigationBar.appearance().setBackgroundImage(UIImage(), forBarPosition: UIBarPosition.Any, barMetrics: UIBarMetrics.Default)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
UINavigationBar.appearance().barTintColor = UIColor.redColor()
UINavigationBar.appearance().translucent = false
UINavigationBar.appearance().clipsToBounds = false
UINavigationBar.appearance().backgroundColor = UIColor.redColor()
UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName : (UIFont(name: "FONT NAME", size: 18))!, NSForegroundColorAttributeName: UIColor.whiteColor()] }
Haven't managed to implement anything different on a specific VC, but this will help 90% of people

Resources