I have applied following code to my application to change the navigation bar image.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController.navigationBar setTintColor:[UIColor blackColor]];
[self setNavigationBarTitle];
}
-(void)setNavigationBarTitle {
UIView *aViewForTitle=[[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 45)] autorelease];
UIImageView *aImg=[[UIImageView alloc] initWithFrame:CGRectMake(-8, 0, 320, 45)];
aImg.image=[UIImage imageNamed:#"MyTabBG.png"];
[aViewForTitle addSubview:aImg]; [aImg release];
UILabel *lbl=[[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 305, 45)] autorelease];
lbl.backgroundColor=[UIColor clearColor]; lbl.font=[UIFont fontWithName:#"Trebuchet MS" size:22];
lbl.shadowColor=[UIColor blackColor]; [lbl setShadowOffset:CGSizeMake(1,1)];
lbl.textAlignment=UITextAlignmentCenter; lbl.textColor=[UIColor whiteColor]; lbl.text=#"Mobile Tennis Coach Overview";
[aViewForTitle addSubview:lbl];
[self.navigationItem.titleView addSubview:aViewForTitle];
}
See following images. You can see the problem that I am facing.
Each view controller of my application has above methods to set the navigation bar background.
How ever, when I push a new view controller to my application. Back button will be appear.
I need back button to be appear. But The image should be behind back button.
Now I am little confused here.
Can you help me regarding this?
Thanks in advance for sharing your knowledge with me.
Thanks a lot.
After an annoying night, I found a slight tweak to this, if you use drawLayer. With drawRect, when you play a video, or youtube video, the navbar is replaced with the image. And I read a few posts that this caused their app to be rejected.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
if([self isMemberOfClass:[UINavigationBar class]])
{
UIImage *image = [UIImage imageNamed:#"navBarBackground.png"];
CGContextClip(ctx);
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextDrawImage(ctx,
CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
else
{
[super drawLayer:layer inContext:ctx];
}
}
#end
If this article is accurate, all should be fine with this approach:
http://developer.apple.com/iphone/library/qa/qa2009/qa1637.html
The short answer is that modifying the structure of the UINavigationBar is not supported by Apple. They really do not want you do be doing what you are trying to do. That is what is causing the issue you are seeing.
Please file a radar requesting this feature so that it can get enough attention to be officially added at some point.
Having said that, to solve the issue you can add a category to UINavigationBar with a -drawRect: method and draw the background image in that method. Something like this will work:
- (void)drawRect:(CGRect)rect
{
static UIImage *image;
if (!image) {
image = [UIImage imageNamed: #"HeaderBackground.png"];
if (!image) image = [UIImage imageNamed:#"DefaultHeader.png"];
}
if (!image) return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
Related
I'm trying to add searching to my UITableViewController via UISearchDisplayController, however I'm seeing a very weird animation glitch when searching begins and ends.
On iPhone, the animation into the navigation bar works fine. However, when ending search, the navigation bar lags behind the search bar while animating down. This causes a white strip to show between the navigation bar and the search bar. On iPad, the animations are completely messed up.
iPhone video
and
iPad video
As the above videos show, the stock apps do not suffer from these animation glitches. Does anybody have any ideas what is causing the issues?
I'm creating the UISearchDisplayController with the following inside the "Master" view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
UISearchBar* searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
searchBar.delegate = self;
self.tableView.tableHeaderView = searchBar;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
}
I also tried doing this using Storyboards but the same animation glitches occur.
Solution for non-translucent navbar (less code, less universality)
The white glitch you see is a tableview's background color which is visible through a gap between navbar and searchbar. The gap is definitely Apple developer's oversight.
So the solution looks like this:
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
UIView *topTableViewBG = [[UIView alloc] initWithFrame:CGRectMake(0, -64, CGRectGetWidth(self.tableView.bounds), 64)];
topTableViewBG.backgroundColor = self.navigationController.navigationBar.backgroundColor;
topTableViewBG.tag = 1234567;
[self.tableView insertSubview:topTableViewBG belowSubview:self.tableView.tableHeaderView];
}
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
[[self.tableView viewWithTag:1234567] removeFromSuperview];
}
here I add a custom view right under the search bar. It happens just before transition to normal VC. The view is positioned at the special point so it covers the gap between the navbar and the searchbar.
After the transition is finished, I remove the custom view.
UPDATE: Universal solution (more code, more universality)
the solution above is good only if there is non-translucent navbar. For translucent navbar there is a challange to find correct color for our 'gap-stopper' view. But as soon we are pretty free to change searchbar color, we can use searchbar color for gap-stopper.
Lets make some changes to our code
First, we need non-translucent searchbar, so set background image to search bar:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[searchBar setBackgroundImage:[UIImage pixelImageWithColor:SEARCHBAR_GRAY_COLOR] forBarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
searchBar.delegate = self;
self.tableView.tableHeaderView = searchBar;
here is the UIImage category method, that was used above:
+ (UIImage *)pixelImageWithColor:(UIColor *)color {
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB ();
CGContextRef ctx = CGBitmapContextCreate (NULL, 1, 1, 8, 0, cs, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault);
CGColorSpaceRelease (cs);
CGContextSetFillColorWithColor (ctx, color.CGColor);
CGContextFillRect (ctx, CGRectMake (0.0f, 0.0f, 1.0f, 1.0f));
CGImageRef cgImage = CGBitmapContextCreateImage (ctx);
CGContextRelease (ctx);
UIImage *result = [UIImage imageWithCGImage:cgImage];
CGImageRelease (cgImage);
return result;
}
Then change our searchDisplayControllerWillEndSearch method:
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
UIView *topTableViewBG = [[UIView alloc] initWithFrame:CGRectMake(0, -64, CGRectGetWidth(self.tableView.bounds), 64)];
topTableViewBG.backgroundColor = SEARCHBAR_GRAY_COLOR;
topTableViewBG.tag = 1234567;
[self.tableView insertSubview:topTableViewBG belowSubview:self.tableView.tableHeaderView];
}
and finally the searchDisplayControllerDidEndSearch method remains unchanged:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
[[self.tableView viewWithTag:1234567] removeFromSuperview];
}
Honestly this solution is more universal and it looks much prettier than one I've described in first part of the answer.
I find the best solution for me, it's looks exactly as I need, I'm using swift but if you need you can easily translate it to objective-c
self.edgesForExtendedLayout = .None
let barBacgroundView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 64))
barBacgroundView.backgroundColor = self.tableView.backgroundColor
UIApplication.sharedApplication().delegate!.window!!.insertSubview(barBacgroundView, atIndex: 0)
Insert this code in viewDidLoad() and that's it. Hope this helps :)
I change UINavigationBar background image with overloaded method
#implementation UINavigationBar (Background)
-(void) drawRect:(CGRect)rect
{
UIImage *image = [UIImage imageNamed:#"Header.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
But in some UIViewControllers I need to remove this background image. How I can do it?
You might look at this blog post from Sebastian Celis. It solves exactly the problem your facing.
And maybe also check this.
test in this way
+ (void)setNavigationbarBackground:(UINavigationBar *)nBar imageName:(NSString *)imgName {
if([nBar respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)]) {
[nBar setBackgroundImage:[UIImage imageNamed:imgName] forBarMetrics:UIBarMetricsDefault];
} else {
[nBar setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:imgName]]];
}
}
2 things i want to do, which are related:
Show a block of any colour. So i could change that colour to something else at any time.
Tint a UIImage to be a different colour. An overlay of colour with alpha turned down could work here, but say it was an image which had a transparent background and didn't take up the full square of the image.
Any ideas?
Another option, would be to use category methods on UIImage like this...
// Tint the image, default to half transparency if given an opaque colour.
- (UIImage *)imageWithTint:(UIColor *)tintColor {
CGFloat white, alpha;
[tintColor getWhite:&white alpha:&alpha];
return [self imageWithTint:tintColor alpha:(alpha == 1.0 ? 0.5f : alpha)];
}
// Tint the image
- (UIImage *)imageWithTint:(UIColor *)tintColor alpha:(CGFloat)alpha {
// Begin drawing
CGRect aRect = CGRectMake(0.f, 0.f, self.size.width, self.size.height);
UIGraphicsBeginImageContext(aRect.size);
// Get the graphic context
CGContextRef c = UIGraphicsGetCurrentContext();
// Converting a UIImage to a CGImage flips the image,
// so apply a upside-down translation
CGContextTranslateCTM(c, 0, self.size.height);
CGContextScaleCTM(c, 1.0, -1.0);
// Draw the image
[self drawInRect:aRect];
// Set the fill color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextSetFillColorSpace(c, colorSpace);
// Set the mask to only tint non-transparent pixels
CGContextClipToMask(c, aRect, self.CGImage);
// Set the fill color
CGContextSetFillColorWithColor(c, [tintColor colorWithAlphaComponent:alpha].CGColor);
UIRectFillUsingBlendMode(aRect, kCGBlendModeColor);
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Release memory
CGColorSpaceRelease(colorSpace);
return img;
}
The first one is easy. Make a new UIView and set its background color to whatever color you’d like.
The second is more difficult. As you mentioned, you can put a new view on top of it with transparency turned down, but to get it to clip in the same places, you’d want to use a mask. Something like this:
UIImage *myImage = [UIImage imageNamed:#"foo.png"];
UIImageView *originalImageView = [[UIImageView alloc] initWithImage:myImage];
[originalImageView setFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
[parentView addSubview:originalImageView];
UIView *overlay = [[UIView alloc] initWithFrame:[originalImageView frame]];
UIImageView *maskImageView = [[UIImageView alloc] initWithImage:myImage];
[maskImageView setFrame:[overlay bounds]];
[[overlay layer] setMask:[maskImageView layer]];
[overlay setBackgroundColor:[UIColor redColor]];
[parentView addSubview:overlay];
Keep in mind you’ll have to #import <QuartzCore/QuartzCore.h> in the implementation file.
Here is another way to implement image tinting, especially if you are already using QuartzCore for something else.
Import QuartzCore:
#import <QuartzCore/QuartzCore.h>
Create transparent CALayer and add it as a sublayer for the image you want to tint:
CALayer *sublayer = [CALayer layer];
[sublayer setBackgroundColor:[UIColor whiteColor].CGColor];
[sublayer setOpacity:0.3];
[sublayer setFrame:toBeTintedImage.frame];
[toBeTintedImage.layer addSublayer:sublayer];
Add QuartzCore to your projects Framework list (if it isn't already there), otherwise you'll get compiler errors like this:
Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
An easy way to achieve 1 is to create a UILabel or even a UIView and change the backgroundColor as you like.
There is a way to multiply colours instead of just overlaying them, and that should work for 2. See this tutorial.
Try this
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* maskedView = [self filledViewForPNG:[UIImage imageNamed:#"mask_effect.png"]
mask:[UIImage imageNamed:#"mask_image.png"]
maskColor:[UIColor colorWithRed:.6 green:.2 blue:.7 alpha:1]];
[self.view addSubview:maskedView];
}
-(UIView*)filledViewForPNG:(UIImage*)image mask:(UIImage*)maskImage maskColor:(UIColor*)maskColor
{
UIImageView *pngImageView = [[UIImageView alloc] initWithImage:image];
UIImageView *maskImageView = [[UIImageView alloc] initWithImage:maskImage];
CGRect bounds;
if (image) {
bounds = pngImageView.bounds;
}
else
{
bounds = maskImageView.bounds;
}
UIView* parentView = [[UIView alloc]initWithFrame:bounds];
[parentView setAutoresizesSubviews:YES];
[parentView setClipsToBounds:YES];
UIView *overlay = [[UIView alloc] initWithFrame:bounds];
[[overlay layer] setMask:[maskImageView layer]];
[overlay setBackgroundColor:maskColor];
[parentView addSubview:overlay];
[parentView addSubview:pngImageView];
return parentView;
}
I've tried the following:
#implementation UIToolbar (Image)
-(void)drawRect:(CGRect)rect {
UIImage *img = [UIImage imageNamed: #"navigationBar.png"];
[img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
Without any success, but it works perfectly with the exact same code for UINavigationBar+Image.
the drawRect method is not getting called for UIToolbar, why is that?
I found a solution
For UIToolBar, if you’re using it within a UINavigationController, you’ll want to also override drawLayer:inContext:, as this appears to be used instead of drawRect: when used within a navigation controller, for some weird reason.
Source: http://atastypixel.com/blog/making-uitoolbar-and-uinavigationbars-background-totally-transparent/
So I implemented - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx instead:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CALayer *imgLayer = [[CALayer alloc] init];
[imgLayer setContents:(id)[[UIImage imageNamed: #"toolbar.png"] CGImage]];
[imgLayer setBounds:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)];
[imgLayer setPosition:CGPointMake(self.bounds.size.width/2,self.bounds.size.height/2)];
[layer insertSublayer:imgLayer atIndex:0];
}
I am adding a custom background for my UINavigationBar. It works fine as long as the phone is in portrait mode. As soon as I switch to landscape mode, half the bar appears blue (the default navbar color) and half of it has my image
How can I stretch the image for landscape mode and make it small again for portrait mode?
Thanks
Solution
Incase anyone is looking for an answer to how to add an image to navigation bar - here goes
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 480.0, 44.0)];
[imgView setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"navbar_landscape" ofType:#"png"]]];
[navigationController.navigationBar addSubview:imgView];
[imgView release];
In both screen orientation modes it's much better to use
[navigationController.navigationBar insertSubview:imgView atIndex:0];
This puts image view under all other views and all the default navigation bar elements (title, standard buttons) work OK.
After a bit of research and trail and error, I found a work around, that will not replace the navbar when you enter the movie playback mode. Hopeefully this does not cause problems with app approval, but based on this post, I think it should be fine:
http://developer.apple.com/iphone/library/qa/qa2009/qa1637.html
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
if([self isMemberOfClass: [UINavigationBar class]]){
UIImage *image = [UIImage imageNamed:#"navBarBackground.png"];
CGContextClip(ctx);
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextDrawImage(ctx, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}else{
[super drawLayer:layer inContext:ctx];
}
}
#end
You probably need to set the autoresizingMask of your background image view; try using UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
The solution Ben gave did solve the problem but it stretches the image in landscape mode. I ended up creating two images- one for landscape and other for the portrait mode. I then added code in shouldAutoRotate to change the navbar image based on the orientation
You can change the image for both portrait and landscape orientations by supplying different images for different orientations by checking the frame size of that UINavigation bar instance:
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
if ([self isMemberOfClass:[UINavigationBar class]] == NO) {
return;
}
UIImage *image = (self.frame.size.width > 320) ?
[UINavigationBar bgImageLandscape] : [UINavigationBar bgImagePortrait];
CGContextClip(context);
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
And this complete demo Xcode project on customizing the appearance of UINavigationBar might be helpful. It also includes #2x versions of background images to support retina displays on iPhone 4 devices.
Try the simpler method of [UIImage imageNamed:#"navbar_landscape.png"] since a UI element is exactly what imageNamed: is intended for, as ljonesATL shows.