ChatGPT解决这个技术问题 Extra ChatGPT

Achieving bright, vivid colors for an iOS 7 translucent UINavigationBar

iOS 7.1 UPDATE: Looks like the workaround for modifying the alpha channel in the UINavigationBar has been ignored in this update. Right now, the best solution seems to be to just 'deal with it' and hope that whatever color you choose can render a translucent effect. I am still looking into ways of getting around this.

iOS 7.0.3 UPDATE: The GitHub library we created has been updated to slightly work around this issue when using iOS 7.0.3. Unfortunately, there is no magic formula to support both colors created in iOS 7.0.2 and earlier and iOS 7.0.3. Seems like Apple improved the saturation, but at the cost of opacity (since the blurred translucency is dependant on the opacity level). I, along with a few others, are working on creating a much better fix for this.

I'm sure many people have already come across the problem where iOS 7 tends to desaturate the color of a UINavigationBar that is translucent.

My goal is to achieve a UINavigationBar with this tint color, but translucent:

https://i.stack.imgur.com/9RjRa.png

However, with translucency, I'm getting this. The background view is white, which I understand will make this view a bit lighter:

https://i.stack.imgur.com/RA5wt.png

Is there any way to achieve the original color while still having translucency? I've noticed Facebook has been able to get their bar to be their rich, blue color, as displayed here:

https://i.stack.imgur.com/C5jXs.png

..so I know there has to be some way. Background views obviously make a difference here, but most of their content is also gray/white. It seems that regardless of whatever bar tint color you put in, you are unable to get vivid colors under translucency.

Updated with solution.

Here's the solution that I ended up coming up with. I took aprato's solution and then encompassed the custom UINavigationBar within a UINavigationController subclass. I have created a repository that has this implementation listed below, along with an example app.

////////////////////////////
// CRNavigationBar.m
////////////////////////////

#import "CRNavigationBar.h"

@interface CRNavigationBar ()
@property (nonatomic, strong) CALayer *colorLayer;
@end

@implementation CRNavigationBar

static CGFloat const kDefaultColorLayerOpacity = 0.5f;
static CGFloat const kSpaceToCoverStatusBars = 20.0f;

- (void)setBarTintColor:(UIColor *)barTintColor {
    [super setBarTintColor:barTintColor];
    if (self.colorLayer == nil) {
        self.colorLayer = [CALayer layer];
        self.colorLayer.opacity = kDefaultColorLayerOpacity;
        [self.layer addSublayer:self.colorLayer];
    }
    self.colorLayer.backgroundColor = barTintColor.CGColor;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    if (self.colorLayer != nil) {
        self.colorLayer.frame = CGRectMake(0, 0 - kSpaceToCoverStatusBars, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + kSpaceToCoverStatusBars);

        [self.layer insertSublayer:self.colorLayer atIndex:1];
    }
}

@end

////////////////////////////
// CRNavigationController.m
////////////////////////////

#import "CRNavigationController.h"
#import "CRNavigationBar.h"

@interface CRNavigationController ()

@end

@implementation CRNavigationController

- (id)init {
    self = [super initWithNavigationBarClass:[CRNavigationBar class] toolbarClass:nil];
    if(self) {
        // Custom initialization here, if needed.    
    }
    return self;
}

- (id)initWithRootViewController:(UIViewController *)rootViewController {
    self = [super initWithNavigationBarClass:[CRNavigationBar class] toolbarClass:nil];
    if(self) {
        self.viewControllers = @[rootViewController];
    }

    return self;
}

@end
isn't Facebook iOS7 UINAvigationBar opaque ?
Nope, it is a much more subtle transparency then the default iOS. Much better, IMO.
Facebook NavigationBar not transparent
It is definitely translucent; please see my edited response.
@Odelya - This is not a solution to obtain the correct colors, but rather a solution to correct the lightness of the UINavigationBar as best as possible when exposed to translucency in iOS 7.

A
Anthony

iOS 7.0.3 UPDATE: As you see above 7.0.3 changed things. I've updated my gist. Hopefully this will just go away as people upgrade.

Original Answer: I ended up with a hack combining the two of the other answers. I'm subclassing UINavigationBar and adding a layer to the back with some extra space to cover if any of the various height status bars are up. The layer gets adjusted in layout subviews and the color changes whenever you set barTintColor.

Gist: https://gist.github.com/aprato/6631390

setBarTintColor

  [super setBarTintColor:barTintColor];
  if (self.extraColorLayer == nil) {
    self.extraColorLayer = [CALayer layer];
    self.extraColorLayer.opacity = self.extraColorLayerOpacity;
    [self.layer addSublayer:self.extraColorLayer];
  }
  self.extraColorLayer.backgroundColor = barTintColor.CGColor;

layoutSubviews

  [super layoutSubviews];
  if (self.extraColorLayer != nil) {
    [self.extraColorLayer removeFromSuperlayer];
    self.extraColorLayer.opacity = self.extraColorLayerOpacity;
    [self.layer insertSublayer:self.extraColorLayer atIndex:1];
    CGFloat spaceAboveBar = self.frame.origin.y;
    self.extraColorLayer.frame = CGRectMake(0, 0 - spaceAboveBar, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + spaceAboveBar);
  }

Where did you implement this on the ViewController? Under viewDidLoad?
This works beautifully! I decided to add this to my UINavigationController subclass. Here's a gist of it for those who are interested in having a custom UINavigationBar within a UINavigationController.
@Jeremy like SpacePyro I have a nav controller subclass (which like the bar I was using pre 7 for UIAppearance targeting) that overrides initWithRootViewController to use this. I've updated my gist
Um…okay..I think I need to see this in an entire test project to fully see what you mean. I was able to replicate timeuser's example below but I'm still a little green with this one.
@Mr.T Thanks! When I wrote it wasn't pushing yet :) But i've updated it now, added UIAppearance for the opacity and support for NSCoding
B
Bhavesh

The behavior of tintColor for bars has changed on iOS 7.0. It no longer affects the bar's background and behaves as described for the tintColor property added to UIView. To tint the bar's background, please use -barTintColor.You can use following code to make the app work with both ios6 and ios7.

if(IS_IOS7)
{
    self.navigationController.navigationBar.barTintColor = [UIColor blackColor];
    self.navigationController.navigationBar.translucent = NO;
}
else
{
    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
}

IS_IOS7 is a macro which is defined in pch file as follows.

#define IS_IOS7 ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0)

J
Jamie Hamick

I didn't come up with this solution but it seems to work fairly well. I just added it to viewDidLoad on my subclass of UINavigationController.

Source: https://gist.github.com/alanzeino/6619253

// cheers to @stroughtonsmith for helping out with this one

UIColor *barColour = [UIColor colorWithRed:0.13f green:0.14f blue:0.15f alpha:1.00f];
UIView *colourView = [[UIView alloc] initWithFrame:CGRectMake(0.f, -20.f, 320.f, 64.f)];
colourView.opaque = NO;
colourView.alpha = .7f;
colourView.backgroundColor = barColour;
self.navigationBar.barTintColor = barColour;
[self.navigationBar.layer insertSublayer:colourView.layer atIndex:1];

It looks like after pushing a new View Controller onto the Navigation controller, any bar button items get moved to be under this transparency layer.
Titles get obscured by this transparency layer, too.
I wrote this gist. The comments are right; either button items are pushed underneath when a new view controller is pushed, or the sublayers are inheriting the alpha of colourView. I forked the other answer and fixed a few things into a new drop–in class: github.com/alanzeino/AZColoredNavigationBar
I was able to put the text white by selecting the Translucent Black Navigation Bar style... this did the trick for me. Also set the yourBar.tintColor = [UIColor whiteColor]; for other custom buttons on the top.
C
Community

One low-fi way would probably be pinning a UIView that is the height of the Navigation Bar to the top of the view behind the bar. Make that view the same color as the navigation bar but play with the alpha until you get the desired effects:

UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.navigationController.navigationBar.frame), 64)];
    backgroundView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:1 alpha:.5];

[self.navigationController.view insertSubview:backgroundView belowSubview:self.navigationController.navigationBar];

UIView behind

https://i.stack.imgur.com/c3CMI.png

(Changed color from lower examples to emphasis transparency. Transparency/blurring is more noticeable when in movement.)

Subclassing the UINavigationBar and placing that same view above the background but behind everything else will probably achieve similar results while being less hacky.

Another solution I've seen tossed around is playing with the alpha of the UINavigationBar:

self.navigationController.navigationBar.alpha = 0.5f;

Edit: Actually, after testing it seems like this doesn't provide the intend behavior (or any behavior):

.8 alpha

https://i.stack.imgur.com/nMhLz.png

Unadjusted alpha

https://i.stack.imgur.com/rorI0.png

Obviously, you will only want to do this on iOS 7 devices. So, add some version check before you implement any of these.


I second the approach of updating the alpha and the tintColor to get the color that you are looking for.
If you can get the desired effects with that less invasive way, then totally. But I'm not sure how effective it will be.
The only issue I'm getting with this (and it doesn't seem represented in your picture) is that it's adding the view directly on top the navigation bar, and as a result, it's causing the controls to be blocked: cl.ly/image/3d3C2o0N283S
Whoops, now I see. Should be [self.navigationController.view insertSubview:backgroundView belowSubview:self.navigationController.navigationBar];
Yeah, I figured that's what you meant. ;) This definitely gives me a better span of colors to pick from. Getting the vibrance of the original color will probably be impossible considering that blur effect still really desaturates anything under it. I'll just have to live with a darker color for now.
b
bernhard

Instead of creating your UIColor object in the RGB format, use HSB and increase the saturation parameter. (Credits to Sam Soffes who describes this method here)

navigationBar.barTintColor = [UIColor colorWithHue:0.555f saturation:1.f brightness:0.855f alpha:1.f];

Note: This solution is a tradeoff and doesn't work well for colors with high saturation.

To pick the HSB color from your design you can use a tool like ColorSnapper which allows you to simply copy the UIColor HSB format.

You can also try the UIColor Category (GitHub Link) from David Keegan to modify existing colors.


s
smad

The problem has now been fixed by Apple in the new 7.0.3 release.


Yes, their tint color implementation has definitely changed in 7.0.3. I can now achieve my desired color by just bumping the saturation up by about 10-15%. Before I also needed to ad the extra layer.
Thanks for letting me know! I'll be pushing an update to my library for this.
And unfixed in 7.1 >_<
M
Mr. T

I used @aprato's solution but found a few corner cases where the new layers from new VCs (eg. UINavigationItemButtonViews, UINavigationItemViews, etc) would be automatically inserted into a position below the extraColorLayer (which would cause those title or button elements to be affected by the extraColorLayer and thus fainter in color than they normally would be). So I adjusted @aprato's solution to force the extraColorLayer to stay at the index position 1. At index position 1, the extraColorLayer stays right above the _UINavigationBarBackground, but underneath everything else.

Here's my class implementation:

- (void)setBarTintColor:(UIColor *)barTintColor
{
    [super setBarTintColor:barTintColor];
    if (self.extraColorLayer == nil)
    {
        self.extraColorLayer = [CALayer layer];
        self.extraColorLayer.opacity = kDefaultColorLayerOpacity;
        [self.layer insertSublayer:self.extraColorLayer atIndex:1]; // This way the text comes out clear
    }
    self.extraColorLayer.backgroundColor = barTintColor.CGColor;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    if (self.extraColorLayer != nil)
    {
        self.extraColorLayer.frame = CGRectMake(0, 0 - kSpaceToCoverStatusBars, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + kSpaceToCoverStatusBars);
    }
}

- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview
{
    [super insertSubview:view aboveSubview:siblingSubview];
    [self.extraColorLayer removeFromSuperlayer];
    [self.layer insertSublayer:self.extraColorLayer atIndex:1]; // This way the text comes out clear
}

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
{
    [super insertSubview:view atIndex:index];
    [self.extraColorLayer removeFromSuperlayer];
    [self.layer insertSublayer:self.extraColorLayer atIndex:1]; // This way the text comes out clear
}

- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview
{
    [super insertSubview:view belowSubview:siblingSubview];
    [self.extraColorLayer removeFromSuperlayer];
    [self.layer insertSublayer:self.extraColorLayer atIndex:1]; // This way the text comes out clear
}

Since layers and subviews aren't mixed, i thing overloading addSubView isn't necessary
A
Allen Hsu

I've improved your code in my fork: https://github.com/allenhsu/CRNavigationController

With my modification, the result color on screen (picked on white background) will be exactly the same value passed into setBarTintColor. I think it's an amazing solution.


@JordanBrown not tested on 7.1 & 8
P
Peter Mortensen

None of these hacks are required :). Simply set:

self.navigationController.navigationBar.translucent = NO;

For iOS 7, the default translucency has been kept to TRUE.


But the question requires translucency as well.
:) Thanks, it worked for me. However there is a 1px black bottom border under the nav bar. Is there any way to remove it?
b
bryguy1300

On a related note, you can set your title text color (with shadow) easily via:

NSShadow *titleShadow = [[NSShadow alloc] init];
titleShadow.shadowOffset = CGSizeMake(0.0f, -1.0f);
titleShadow.shadowColor = [UIColor blackColor];
NSDictionary *navbarTitleTextAttributes = @{NSForegroundColorAttributeName: [UIColor whiteColor],
                                            NSShadowAttributeName: titleShadow};
[[UINavigationBar appearance] setTitleTextAttributes:navbarTitleTextAttributes];

If you're trying to match iOS 7's style, using a drop shadow on your title text is probably inappropriate.
You're correct, in iOS 7 Apple is no longer setting shadows on their navbar text. If you wanted a custom look though, this would give you the option.
b
bizz84

I came across this Q/A while trying to setup an uniformly colored navigation bar with transparency DISABLED on iOS 7.

After experimenting a while with barTintColor I figured out that a very easy way of having an opaque navigation bar is to make a single pixel image of the desired color, make a stretchable image out of it, and setting it to the backgroundImage of the navigation bar.

UIImage *singlePixelImage = [UIImage imageNamed:@"singlePixel.png"];
UIImage *resizableImage = [singlePixelImage resizableImageWithCapInsets:UIEdgeInsetsZero];
[navigationBar setBackgroundImage:resizableImage forBarMetrics:UIBarMetricsDefault]; 

Three lines of code, very simple and works BOTH on iOS 6 and iOS 7 (barTintColor is unsupported on iOS 6).


D
DoctorG

Theres a great Dropin UINavigationController replacement available from Simon Booth available at GitHub Here GitHub - C360NavigationBar

If you're backward supporting iOS6 do a check on the root view controller as such:

PatientListTableViewController *frontViewController = [[PatientListTableViewController alloc] init];

    UINavigationController *navViewController = [[UINavigationController alloc] initWithNavigationBarClass:[C360NavigationBar class] toolbarClass:nil];
if ([navViewController.view respondsToSelector:@selector(setTintColor:)]) {
    //iOS7
    [navViewController.view setTintColor:self.navBarTintColor];
    [[C360NavigationBar appearance] setItemTintColor:self.navBarItemTintColor];
} else {
    //iOS6
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:NO];
    navViewController.navigationBar.tintColor = self.navBarTintColor;
}
[navViewController pushViewController:frontViewController animated:NO];

self.window.rootViewController = navViewController;

Unless I'm missing something, this library's bars are not translucent.
C
Community

As @bernhard mentioned above it's possible to saturate the bar tint color to get desired navigation bar appearance.

I wrote an BarTintColorOptimizer utility for that kind of adjustment. It optimizes translucent bar tint color to make the bar's actual color match the desired color in iOS 7.x and later. Look at this answer for details.


S
Sagar Kothari

Frankly speaking, above answers might be right but following trick worked for me with very ease.

// this is complete 100% transparent image
self.imageBlack = [[UIImage imageNamed:@"0102_BlackNavBG"] 
           resizableImageWithCapInsets:UIEdgeInsetsMake(0, 2, 0, 2)  
                          resizingMode:UIImageResizingModeStretch];

// this is non-transparent but iOS7 
// will by default make it transparent (if translucent is set to YES)
self.imageRed = [[UIImage imageNamed:@"0102_RedNavBG"] 
         resizableImageWithCapInsets:UIEdgeInsetsMake(0, 2, 0, 2)  
                        resizingMode:UIImageResizingModeStretch];

// some navigation controller
[nvCtrLeft.navigationBar setBackgroundImage:self.imageRed 
                              forBarMetrics:UIBarMetricsDefault];

// some another navigation controller
[nvCtrCenter.navigationBar setBackgroundImage:self.imageRed 
                                forBarMetrics:UIBarMetricsDefault];

Here are the images used for self.imageRed and self.imageBlack.

https://i.stack.imgur.com/EFhlm.png

https://i.stack.imgur.com/rlgmu.png


This doesn't give you blur.
A
Ashish Kakkad

is there a way to use @aprato solution without subclassing UINavigationBar.

In my project my main view is a UIViewController.

the problem is that the navigationController is a readonly property, is there a way to use you class with my project because i can't use : [[UINavigationController alloc] initWithNavigationBarClass:

thanks


Couldn't you just wrap your UIViewController inside a UINavigationController with [[[UINavigationController alloc] initWithRootViewController:<YourViewController>]? This may also be better answered as a separate question, rather than posting here.
C
Carter

An easy way to get the color you want is using

    [<NAVIGATION_BAR> setBackgroundImage:<UIIMAGE> forBarPosition:<UIBARPOSITION> barMetrics:<UIBARMETRICS>];

As long as your image has some alpha, the translucency will work and you can set the alpha by changing the image. This was just added in iOS7. The width and height for the image are 640x88px for vertical (add 20 to the 88 if you want it to be underneath the status bar).


This does not retain blur however. There is transparency, but no blur.