I've been trying to set up a custom background for the whole of my NavigationBar (not just the titleView) but have been struggling.
I found this thread
http://discussions.apple.com/thread.jspa?threadID=1649012&tstart=0
But am not sure how to implement the code snippet that is given. Is the code implemented as a new class? Also where do I instatiate the UINavigationController as I have an application built with the NavigationView template so it is not done in my root controller as per the example
Uddhav and leflaw are right. This code works nicely:
#interface CustomNavigationBar : UINavigationBar
#end
#implementation CustomNavigationBar
-(void) drawRect:(CGRect)rect
{
UIImage *image = [UIImage imageNamed: #"myNavBarImage"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
// this can go anywhere
+(UINavigationController*) myCustomNavigationController
{
MyViewController *vc = [[[MyViewController alloc] init] autorelease];
UINavigationController *nav = [[[NSBundle mainBundle] loadNibNamed:#"CustomNavigationController" owner:self options:nil] objectAtIndex:0];
nav.viewControllers = [NSArray arrayWithObject:vc];
return nav;
}
You have to create CustomNavigationController.xib and put a UINavigationController in it and change the navigationBar class to "CustomNavigationBar".
You must use the 'appearance' proxy to change the background and other styling properties of controls such as UINavigationBar, UIToolBar etc. in iOS 5.xx. However, these are not available for iOS 4.xx so for backwards compatibility, you need a hybrid solution.
If you want to support both iOS 4.xx and iOS 5.xx devices (i.e. your DeploymentTarget is 4.xx), you must be careful in wrapping the call to the appearance proxy by checking at runtime if the 'appearance' selector is present or not.
You can do so by:
//Customize the look of the UINavBar for iOS5 devices
if ([[UINavigationBar class]respondsToSelector:#selector(appearance)]) {
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:#"NavigationBar.png"] forBarMetrics:UIBarMetricsDefault];
}
You should also leave the iOS 4.xx workaround that you may have implemented. If you have implemented the drawRect workaround for iOS 4.xx devices, as mentioned by #ludwigschubert, you should leave that in:
#implementation UINavigationBar (BackgroundImage)
//This overridden implementation will patch up the NavBar with a custom Image instead of the title
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"NavigationBar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
This will get the NavBar look the same in both iOS 4 and iOS 5 devices.
You just have to overload drawRect like that :
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"NavigationBar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
Implementing a category is not advisable. iOS5 may provide relief for this issue. But for old APIs, you can -
Subclass UINavigationBar to say CustomNavBar and implement the custom drawRect from Lithium's answer.
For all IB based UINavigationControllers, provide CustomNavBar as custom class for their UINavigationBar.
For all code based UINavigationControllers. Create a XIB with a UINavigationController and do step two. Then provide a factory method in code that loads the UINavigationController from the nib and provide an IBOutlet.
Eg.
[[NSBundle mainBundle] loadNibNamed:#"CustomNavigationController" owner:self options:nil];
UINavigationController *navController = self.customNavigationController;
navController.viewControllers = [NSArray arrayWithObject:controller]
You can also override the drawLayer:inContext: method in a UINavigationBar category class. Inside the drawLayer:inContext: method, you can draw the background image you want to use.
- (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 as a complete demo Xcode project on customizing the appearance of UINavigationBar this and this might be helpful.
Implementing a category won't work in iOS5, you should use Uddhav Kambli's advice for using CustomNavbar on iOS ≤ 5.
I just found this blog entry, describing this topic very simple: http://web0.at/blog/?p=38
it helped me a lot, they use the "drawRect" method to get the customisation of the background.
To all those who are having trouble with UINavigationBar custom backgrounds in iOS5, do this in the corresponding viewDidLoad methods:
#if defined(__IPHONE_5_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_5_0
if ([self.navigationController.navigationBar respondsToSelector:#selector( setBackgroundImage:forBarMetrics:)]){
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"TitleBar"] forBarMetrics:UIBarMetricsDefault];
}
#endif
Notice that in my case, the background image was named "TitleBar". You can put whatever your custom background image name is.
The problem you'll have is that if you use a navigation controller, the title of each page will overlay your custom navbar. If your navbar contains a logo or the name of your app, this is obviously unacceptable.
You could set the title of each view in your navigation stack to blank, but some views force a title that you can't do anything about (like the photo picker). So you might want to create an alternate navbar image with the same color or design as your logo navbar, but with a blank area to make room for overlaid titles.
To switch navbar images at will, add a property to your app delegate to hold the name of the navbar image and replace the first line of the first example above with these two:
YourAppDelegate* theApp = (YourAppDelegate*)[[UIApplication sharedApplication] delegate];
UIImage* image = [UIImage imageNamed:theApp.navBarName];
Then in the first view controller that you'll push onto the navigation stack, do something like this:
- (void)viewWillAppear:(BOOL)animated
{
YourAppDelegate* theApp = (YourAppDelegate*)[[UIApplication sharedApplication] delegate];
theApp.navBarName = #"navBar_plain";
}
Then in the root view controller, do the same thing but specify your logo-bearing navbar image, so it gets restored when the user navigates back to it and there is no conflicting title.
Another approach is to Use UINavigationController's delegate.
It doesn't require subclassing/overwriting the UINavigationBar class:
/*
in the place where you init the navigationController:
fixer = [[AENavigationControllerDelegate alloc] init];
navigationController.delegate = fixer;
*/
#interface AENavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
#end
#implementation AENavigationControllerDelegate
#define bgImageTag 143
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
//this is for the future for implementing with the appearance api:
if ([[navigationController navigationBar] respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)])
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[navigationController navigationBar] setBackgroundImage:[UIImage imageNamed:#"header-logo-bg.png"]
forBarMetrics:UIBarMetricsDefault];
});
}
else
{
UIImageView* imageView = (UIImageView*)[navigationController.navigationBar viewWithTag:bgImageTag];
if(!imageView)
{
UIImage *image = [UIImage imageNamed:#"header-logo-bg.png"];
imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
imageView.tag = bgImageTag;
}
[navigationController.navigationBar insertSubview:imageView atIndex:0];
}
}
#end
https://gist.github.com/1184147
In iOS5, zPosition value (of UINavigationBar's most depth layer) is changed. So if you change that zPosition, the old way works.
eg.
UINavigationBar *_bar = navigationController.navigationBar;
// Insert ImageView
UIImage *_img = [UIImage imageNamed:#"navibar.png"];
UIImageView *_imgv = [[[UIImageView alloc] initWithImage:_img] autorelease];
_imgv.frame = _bar.bounds;
UIView *v = [[_bar subviews] objectAtIndex:0];
v.layer.zPosition = -FLT_MAX;
_imgv.layer.zPosition = -FLT_MAX+1;
[_bar insertSubview:_imgv atIndex:1];
This script handle view's layer, so You should import QuartzCore.
Here is an alternative solution that lets you use your own custom subclass of UINavigationBar:
https://gist.github.com/1253807
As Apple itself has said, it is not correct to override methods in Categories. So the best way to customize the background of UINavigarionBar is subclassing and override -(void)drawInRect: method.
#implementation AppNavigationBar
- (void)drawRect:(CGRect)rect
{
UIImage *patternImage = [UIImage imageNamed:#"image_name.png"];
[patternImage drawInRect:rect];
}
To use this customized UINavigationBar it should be set as navigationBar property of your UINavigationBarController. As you know this property is readonly. So what should be done is:
- (void)viewDidLoad
{
[super viewDidLoad];
AppNavigationBar *nav = [AppNavigationBar new];
[self setValue:nav forKey:#"navigationBar"];
}
It works for both iOS 5 and 4.3.
You can subclass UINavigationBar and enable it like this, since categories for drawRect won't work in iOS5 anymore
navigationController = [[((^ {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:[NSKeyedArchiver archivedDataWithRootObject:navigationController]];
[unarchiver setClass:[SAPHUINavigationBar class] forClassName:#"UINavigationBar"];
[unarchiver setClass:[UIViewController class] forClassName:NSStringFromClass([navigationController.topViewController class])];
return unarchiver;
})()) decodeObjectForKey:#"root"] initWithRootViewController:navigationController.topViewController];
For a static view (no animation at all), I use the default iOS setBackgroundImage
But when I have a view that's animated (resize most likely), I create a custom UIImageView and add it to the navigationBar so that I have more flexibility over it
The thing is if you just add it, it will get on top of the buttons and the titleView, so I manually save a copy of most of subviews, remove them from parent view, add my imageView and than add all the subviews back
This works for me
UIImageView *navBackground = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"navigationBackgroundSample.jpg"]];
UIView *tempTitleView = [[self.navigationBar.subviews objectAtIndex:1] autorelease];
[[self.navigationBar.subviews objectAtIndex:1] removeFromSuperview];
[self.navigationBar addSubview:navBackground];
[self.navigationBar addSubview:tempTitleView];
self.navigationBar.clipsToBounds = YES;
[navBackground release];
In this case, I don't have buttons and I found out that my titleView is at index 1, if you have buttons, they should be around somewhere in the subviews array of navigationBar
I don't know what's at index 0, I don't know if this can work around the case you have text title neither...
Related
[[[UINavigationBar appearance] setTitleView:button]
I tried to set title view as button for all navigation item using above line of code but it is not working.How can I set title view for all navigation bars in my project by writing small code in appdelegate ?
Customizing the Appearance of a Navigation Bar
it is alright to modify the barStyle, tintColor, and translucent
properties, but you must never directly change UIView-level properties
such as the frame, bounds, alpha, or hidden properties directly.
For more detail you can follow apple doc UINavigationBar Class Reference
Edit ->
I solved your query
[[UINavigationBar appearance] addSubview:yourView];
Other Navigation Related query
try this dude...
//put whatever object in this view..for example button that you want to put...
UIView* ctrl = [[UIView alloc] initWithFrame:navController.navigationBar.bounds];
ctrl.backgroundColor = [UIColor yellowColor];
ctrl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[navController.navigationBar addSubview:ctrl];
let me know it is working or not!!!!
Happy Coding!!!
This is how you create a navigation controller in addDelegate and set a title to it,
you have to add the following code to didFinishLaunchingWithOptions method.
Notice that you need to set a root view for the viewController which will the viewController that will be displayed inside the navigationController.
yourViewController *mainVC = [[yourViewController alloc]init];
UINavigationController *navigationController1 = [[UINavigationController alloc] initWithRootViewController:mainVC];
navigationController1.title = #"title";
[window addSubview:navigationController1.view];
If you use iOS4, you can override drawRect
#implementation UINavigationBar (UINavigationBarCategory)
-(void)drawRect:(CGRect)rect
{
UIImageView *itleImageView = //create object
[self addSubView:titleImageView];
//set title view in navigation bar
}
#end
iOS 5,
if ([[UIDevice currentDevice].systemVersion floatValue] >= 5.0) {
if ([self.navigationController.navigationBar respondsToSelector:#selector( setBackgroundImage:forBarMetrics:)]){
UIImageView *itleImageView = //create object
[self.navigationController.navigationBar addSubView:titleImageView];
}
}
i guess this is a right, as i don't have implemented.
I wanted to add a logo in every NavigationController, so I made a subclass of
UINavigationController and used this in the viewDidLoad-Method
UIImage *logo =[UIImage imageNamed:#"logo"];
CGFloat navWidth = self.navigationBar.frame.size.width;
CGFloat navHeight = self.navigationBar.frame.size.height;
UIImageView *logoView = [[UIImageView alloc] initWithFrame:CGRectMake(navWidth-logo.size.width - 5,
(navHeight - logo.size.height) / 2,
logo.size.width,
logo.size.height)];
logoView.image = logo;
[self.navigationBar addSubview:logoView];
I currently have a uinavigationbar in my app with a custom image for the background.
I've implemented this using the category technique. Something like this:
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"NavigationBar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
I guess it is also possible to method swizzling but from everything I've read this isn't a great approach as UIKit changes will break my code.
The problem I have with approach this is changing the background image when new view controllers are popped off the stack. I seem to be able to modify the image when they are pushed but when I pop the view controller I am not able to change the image back. Is there a way I can listen for the user taps the back button on the navigationcontroller and trigger an image change at that point?
Also curious as to whether this kind of practice is discouraged by Apple.
You could try to use this method to set the background image programmatically. Link to Apple Docs
willShowViewController:animated
you could try this way
create a global constant variable.
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
UIImage *image =nil;
if(CONSTANT==1)
image= [UIImage imageNamed: #"NavigationBar.png"];
else
image= [UIImage imageNamed: #"newbar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
in UIViewController
-(void)viewWillAppear:(BOOL)animated{
CONSTANT=2;//CONSTANT=1
[self.navigationController.navigationbar setNeedsDisplay];
}
I am trying to create a UINavigationController with a background Image...
I have the following code at the top of the class where I implement the UINavigationController.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIImage *img = [UIImage imageNamed:#"header.jpg"];
[img drawInRect:CGRectMake(0, 0, self.frame.size.width,self.frame.size.height)];
}
#end
Then, inside my #implementation of my controller in the "viewDidLoad" function, I have the following...
MainViewController *controller = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:controller];
self.navController = nav;
[nav release];
I know I am close, because everything is working almost perfectly, except the image is 150px in height, and it's being squished down to a smaller size (doing a log of self.frame.size.height is giving me 44.0000) but is pushed down a certain number of pixels from the top...
I know I am close, but if anyone could help me out, it would be muchly appreciated.
Thank you,
--d
You really need to actually make your image the same size as your navigation bar. Don't try to do this unless you really have art that was made for a UINavigationBar.
This code will work, so long as the dimensions are correct:
- (void)drawRect:(CGRect)rect {
[img drawInRect:rect];
}
To support different orientations with differently-sized navigation bars, just send -[UIApplication statusBarOrientation] (I've heard from a lot of people that -[UIDevice orientation] doesn't work as expected, but I haven't tried it).
I am building an iPhone Utility app that uses UIImageView to display an animation. Within the MainViewController's viewDidLoad() method, I am creating an instance of a CustomClass, and then setting up the animations:
- (void)viewDidLoad
{
[super viewDidLoad];
cc = [CustomClass new];
NSArray * imageArray = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"image-1-off.jpg"],
[UIImage imageNamed:#"image-2-off.jpg"],
[UIImage imageNamed:#"image-3-off.jpg"],
[UIImage imageNamed:#"image-4-off.jpg"],
nil];
offSequence = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
offSequence.animationImages = imageArray;
offSequence.animationDuration = .8;
offSequence.contentMode = UIViewContentModeBottomLeft;
[self.view addSubview:offSequence];
[offSequence startAnimating];
}
That works fine. However, I would like to be able to move all the above code that sets up the UIImageView into my CustomClass. The problem is in the second to last line:
[self.view addSubview:offSequence];
I basically need to replace 'self' with a reference to the MainControllerView, so I can call addSubview from within my CustomClass. I tried creating an instance var of CustomClass called mvc and a setter method that takes a reference to the MainViewController as an argument as such:
- (void) setMainViewController: (MainViewController *) the_mvc
{
mvc = the_mvc;
}
And then I called it within MainViewController like so:
[cc setMainController:MainViewController:self];
But this yields all sorts of errors which I can post here, but it strikes me that I may be overcomplicating this. Is there an easier way to reference the MainViewController that instanatiated my CustomClass?
The cleanest way to do this would be to create a subclass of UIImageView and create a customize initializer that accepts the array. So,
#interface MyCustomImageView:UIImageView {
//...
}
-(id) initWithFrame(CGRect) aRect animationImages:(NSArray *) imageArray;
#end
Then in your main view controller just initialize the custom image view, populate it and add it to the subviews. This will make everything nicely encapsulated, modular and reusable.
My problem is that I can't seem to get the image from my bundle to display properly. This method is in the view controller that controls the tableview. headerView is loaded with the tableview in the .nib file and contains a few UILabels (not shown) that load just fine. Any ideas?
- (void)viewDidLoad {
[super viewDidLoad];
[[self view] setTableHeaderView:headerView];
NSBundle *bundle = [NSBundle mainBundle];
NSString *imagePath = [bundle pathForResource:#"awesome_lolcat" ofType:#"jpeg"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
imageView = [[UIImageView alloc] initWithImage:image];
}
If you've already created an outlet and connected it to a view in Interface Builder, you should use that view, rather than creating a UIImageView on the fly.
//this assumes that headerView is an already created UIView, perhaps an IBOutlet
//also, imageViewOutlet is an IB outlet hooked up to a UIImageView, and added as
//a subview of headerView.
//you can also set the image directly from within IB
imageViewOutlet.image = [UIImage imageNamed: #"awesome_lolcat.jpeg"];
[[self view] setTableHeaderView: headerView];
FIrst you need to figure out whether your image is loading properly. The quickest way to get an image is to use the UIImage convenience method +[UIImage imageNamed: rawImageName].
Also, is this a UITableViewController? (it's unclear, but implied).
Where is imageView being used? You create it near the bottom, but don't seem to do anything with it. You probably want to create the image view, assign it an image, and then add it as a subview to the headerView.
//this assumes that headerView is an already created UIView, perhaps an IBOutlet
UIImage *image = [UIImage imageNamed: #"awesome_lolcat.jpeg"];
UIImageView *imageView = [[UIImageView alloc] initWithImage: image];
[headerView addSubview: [imageView autorelease]];
[[self view] setTableHeaderView: headerView];
Adding the subview programatically worked, but it isn't correctly linked to the UIImageView in Interface Builder. The view I created is (I think) correctly linked to the UIView outlet in my UITableViewController, but when I init my imageView it creates a new view instead of putting the image in the view I already created.
Thanks for your answer.
See http://developer.apple.com/library/ios/#samplecode/TableViewUpdates/Introduction/Intro.html
It will display UIImageview on section header.