95

When I am navigating back & forth between parent and child controllers in a master - detail navigation controller, i see a dark shadow on the right side of navigation bar at top. It started after I upgraded to Xcode 5.1. It feels rough and distracting. How can I get rid of it?

Wayne Chen
  • 305
  • 2
  • 15
Nihat
  • 3,055
  • 3
  • 18
  • 28

13 Answers13

145
self.navigationController.view.backgroundColor = [UIColor whiteColor];

I solved this problem by setting the background color of the navigation controller's view.

nonamelive
  • 6,510
  • 8
  • 40
  • 47
  • This answer is actually very good. For some reason Interface Builder doesn't allow you to access the view of your navigation controller, but looks like a `darkColor` view is still there and causes this problem. – superarts.org Feb 23 '15 at 06:38
  • 1
    This is a great answer because it also allows the bar to remain translucent while not showing the ugly black section that bleeds through from the navigation controller. Just wish there was a way to set it up in the storyboard. – dimiguel Aug 23 '15 at 01:23
  • Exactly. From time to time I think about it and get a bit disappointed about the other answers that suggest turning off navigation bar transparency, since basically they are solving this problem by disabling a feature, which this answer points out the actual fix. Too bad this behavior remains the same in Xcode 7 / iOS 9. – superarts.org Oct 17 '15 at 14:03
  • 1
    Sorry, I downvoted this answer because window background isn't the root cause to this problem. Please see my screenshot attached: http://imgur.com/a/SH5Dp You will find the problem still exists, the dark shade just replaced to white one, I guess details controller got 'clipped' or somehow, make it doesn't draw anything under NavBar. – mariotaku Oct 18 '16 at 02:38
  • 1
    tabBarController?.view.backgroundColor = UIColor.white incase if root controller is UITabBarController. – Vishal Singh Jan 05 '17 at 18:34
57
self.navigationController.navigationBar.translucent = NO; 

For Newer Swift Versions:

navigationController?.navigationBar.isTranslucent = false
PhillipJacobs
  • 2,337
  • 1
  • 16
  • 32
Nihat
  • 3,055
  • 3
  • 18
  • 28
37

nonamelive's answer is perfect. To achieve the same thing in Interface Builder AND STILL KEEP TRANSLUCENCY, select the navigation controller and set a user defined runtime attribute view.backgroundColor as shown in the screenshot (in the Identity Inspector). Repeat for all navigation controllers that show this problem.

It seems that this whole problem occurs because the black color (or actually, no color) of UINavigationController is leaking through at the time CoreGraphics snapshots it at animation begin. So, setting it to white will prevent that.

Identity Inspector -> User Defined Runtime Attributes

manmal
  • 3,900
  • 2
  • 30
  • 42
  • 1
    I prefer this approach, let Interface Builder UI stuff as much as possible. – DazChong Dec 24 '15 at 02:45
  • iOS 8.4 did not helped – Yaroslav Dukal Nov 10 '16 at 00:24
  • 3
    Works perfectly with Xcode 8.3.3. Just to re-emphasize, must be set on `UINavigationController`, not on the viewController. – jungledev Jul 06 '17 at 21:17
  • I had a navcon in a tabcon and saw shadows on both bars (top and bottom) when using "Hides bottom bar on push" on one of the navcon's VCs. Setting the white background on the navcon fixed both shadows. Thanks! – nh32rg Mar 09 '20 at 20:13
6

This seems to be a bug that was introduced in iOS 7.1. In my case it is caused by a UIToolbar placed directly below the navigation bar. The dark shadow also appears in the translucent tab bar.

The shadow seems to be caused by the background view of the UIToolbar. I now use this workaround in the view controller with the toolbar that hides the toolbar's background view during the transition:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    UIView *toolbarBackgroundView = [self.toolbar findViewRecursively:^BOOL(UIView *subview, BOOL *stop) {
        BOOL isToolbarBackgroundView = ([subview isKindOfClass:[UIImageView class]]
                                        && [NSStringFromClass(subview.class) isEqualToString:@"_UIToolbarBackground"]);
        if (isToolbarBackgroundView) {
            *stop = YES;
        }
        return (! isToolbarBackgroundView);
    }];
    if (toolbarBackgroundView) {
        // fade toolbar background view back in
        [UIView animateWithDuration:0.1f animations:^{
            toolbarBackgroundView.alpha = 1.0f;
        }];
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    UIView *toolbarBackgroundView = [self.toolbar findViewRecursively:^BOOL(UIView *subview, BOOL *stop) {
        BOOL isToolbarBackgroundView = ([subview isKindOfClass:[UIImageView class]]
                                        && [NSStringFromClass(subview.class) isEqualToString:@"_UIToolbarBackground"]);
        if (isToolbarBackgroundView) {
            *stop = YES;
        }
        return (! isToolbarBackgroundView);
    }];
    if (toolbarBackgroundView) {
        // hide toolbar background view
        toolbarBackgroundView.alpha = 0.0f;
    }
}

This is the code for [UIView findViewRecursively:]

@interface UIView (FindSubview)

- (UIView*)findViewRecursively:(BOOL(^)(UIView* subview, BOOL* stop))recurse;

@end

@implementation UIView (FindSubview)

- (UIView*)findViewRecursively:(BOOL(^)(UIView* subview, BOOL* stop))recurse {
    for (UIView* subview in self.subviews) {
        BOOL stop = NO;
        if (recurse(subview, &stop)) {
            UIView* view = [subview findViewRecursively:recurse];
            if (view) return view;
        } else if (stop) {
            return subview;
        }
    }
    return nil;
}

@end

I filed this Radar: http://openradar.appspot.com/16418845

tom
  • 937
  • 9
  • 13
  • 2
    Your solution is fine if you don't want a translucent navigation bar. – tom Mar 25 '14 at 19:48
  • There's an easier way to get the `backgroundView`. `[self.toolbar valueForKey:@"_backgroundView"]`. Please note this is a private API, but I think you will not be caught by Apple because `_backgroundView` is just a generic name. – nonamelive Oct 27 '14 at 23:33
  • This answer tipped me off to what I needed to do. In my case it was as simple as unchecking the translucent option on the UIToolbar in interface builder. – Greg W Nov 16 '18 at 16:26
5

This works for me in Swift

In AppDelegate on didFinishLaunchingWithOptions method, I set this:

UIApplication.shared.windows.first?.backgroundColor = .white
pableiros
  • 14,932
  • 12
  • 99
  • 105
5

This works for me on iOS 13 with both light and dark themes and also on older iOS versions.

Add the following code to the AppDelegate to the application(didFinishLaunchingWithOptions) method:

if #available(iOS 13.0, *) {
    window?.backgroundColor = UIColor.systemBackground
} else {
    window?.backgroundColor = UIColor.white
}
petrsyn
  • 5,054
  • 3
  • 45
  • 48
  • 2
    I too tried this method, but I face an issue when presenting a view controller in default mode. Then you will see the white background of window instead of black one. That looks weird. can you please suggest any idea to overcome this situation – varun v nair Nov 08 '19 at 11:44
4

It seems to happen with any bar (TabBar or ToolBar) that is translucent.
So one way to fix it is to set the _tabBar.translucent = NO; (in my case). This prevents the undesired shadow under the top navigation bar while leaving the navigation bar translucent. Unfortunately the bottom bar is no longer translucent though.

It can be set back to translucent but all this has to happen after the whole pushing animation is finished thus switching this property is well noticeable.

In case, however the bottom bar also has to be translucent and I don't want the user to see the change I resolved it with the following:

/*  create a simple quick animation of the bottom bar
    just before pushing the new controller */
[UIView animateWithDuration:0.1
                 animations:^{
                     _tabBar.barTintColor = [UIColor colorWithWhite:0.97254901960784 alpha:1.0]; // this is the closest color for my case
                     _tabBar.translucent = NO;
                 } completion:^(BOOL finished) {
                     /* now when the animation that makes the bar not translucent
                        is finished we can push the new controller
                        the controller is instantiated before the animation code */
                     [self.navigationController pushViewController:controller animated:YES];
                 }];

Then in the viewDidAppear: I simply reverts that back:

[UIView animateWithDuration:0.1
             animations:^{
                     _tabBar.barTintColor = nil;
                     _tabBar.translucent = YES;
                 }];

There is just a little change in the appearance especially but it's barely noticeable and it's way better than having the shadow under the navigation bar.

Hope it'll help others to keep bars translucent until Apple fix this behaviour as bars ARE meant to be hidden in some cases unlike it was suggested in other posts especially for the UITabBar

kacho
  • 371
  • 2
  • 14
  • I was able to fix this problem by adopting @manmal's solution—define the runtime attribute `view.backgroundColor` for your UITabBarController in the storyboard, and set it to a white colour. – jamesk Dec 14 '15 at 04:14
3

Here is my variation...it requires much less code than tom's answer, and is more efficient. This is IF you want a translucent navigation bar, and also want to fix that shadow problem.

In the source ViewController (that is embedded in the Navigation Controller)...

- (void)viewDidAppear:(BOOL)animated
{
     self.navigationController.navigationBar.translucent = YES;
}

and

 - (void)viewWillDisappear:(BOOL)animated
 {
     self.navigationController.navigationBar.translucent = NO;
 }

The result is the same as what Tom does (visually, to the end user), and is easier to implement. Hope this helps...

user2734823
  • 147
  • 1
  • 10
3
self.navigationController!.navigationBar.translucent = false;

This works for me place it inside the function where you push the new ViewController

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Shyam Raju
  • 87
  • 1
  • 1
  • 7
3

The following also works and leaves the Navigation Bar transparent:

[UIApplication sharedApplication].keyWindow.backgroundColor = [UIColor whiteColor];

seb
  • 116
  • 7
1

While it's not the same as the stock iOS implementation, this is a nice way to fix the problem:

- (void)viewWillAppear:(BOOL)animated {
    [UIView animateWithDuration:0.35f animations:^{
        self.tabBarController.tabBar.alpha = 1.0f;
    }];
}

- (void)viewWillDisappear:(BOOL)animated {
    [UIView animateWithDuration:0.35f animations:^{
        self.tabBarController.tabBar.alpha = 0.0f;
    }];
}

You'll get a nice fade-in/fade-out animation of the tab bar. Add the code in the root UIViewController.

nikolovski
  • 4,049
  • 1
  • 31
  • 37
0

For those, who have implemented tabBar and want to have both nav and tab bar still translucent, I found an easy workaround after dealing with painful "tabBar snapshot while push" workaround for a two years.

The trick is to:

  1. set clear backgroundView on the tab bar, that cause to layout the view controllers differently
  2. set a new BlurEffect view below first button
  3. constraint the blur view to tabBar (UIView)

Before I was using a snapshot of the tabBar and was setting alpha of the tabBar to 0, but that cause unwanted safeLayoutGuide offsets. As this solution does now access any private variables, I hope this is green to go to AppStore (I'm not yet there).

In viewDidLoad of my UITabBarController I set following:

tabBar.backgroundImage = UIImage()
let blurView = UIVisualEffectView()
blurView.effect = UIBlurEffect(style: .systemChromeMaterial)
blurView.frame = tabBar.bounds
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.isUserInteractionEnabled = false
tabBar.insertSubview(blurView, belowSubview: tabBar.subviews.first!)
let leadingConstraint = blurView.leadingAnchor.constraint(equalTo: tabBar.leadingAnchor, constant: 0)
let trailingConstraint = blurView.trailingAnchor.constraint(equalTo: tabBar.trailingAnchor, constant: 0)
let topConstraint = blurView.topAnchor.constraint(equalTo: tabBar.topAnchor, constant: 0)
let bottomConstraint = blurView.bottomAnchor.constraint(equalTo: tabBar.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])

or:

//Setting background image to empty image to prevent a bug under top right navigation bar corner
tabBar.backgroundImage = UIImage()
//As that turns of the blur effect I am adding a new view imitating the same
let blurView = UIVisualEffectView()
blurView.effect = UIBlurEffect(style: .systemChromeMaterial)
blurView.frame = tabBar.bounds
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurView.isUserInteractionEnabled = false
tabBar.insertSubview(blurView, belowSubview: tabBar.subviews.first!)
Lukas Smilek
  • 41
  • 1
  • 7
-1

Or if you're using interface builder, you can just select Navigation Bar from your navigation controller and uncheck the Translucent checkbox between Style and Bar Tint in the Attributes Inspector to get rid of that weird effect -

Inspector

0xC0DED00D
  • 19,522
  • 20
  • 117
  • 184