I'm working on customizing the appearance of navigation controllers in my app to look like the following:
As I've discovered after a few hours of SO research, there are a ton of different ways of doing it, some really hackish, some much less so. I'm interested in finding out the Apple-blessed / most elegant way of achieving this that will lead to the least amount of pain down the road as the app grows. Some approaches I've looked into so far:
1) I changed the background / height of the navigation bar by applying an image through [UINavigationBar appearance], seems to have worked fine.
UIImage *navBarImage = [UIImage imageNamed:#"navigation-bar.png"];
[[UINavigationBar appearance] setBackgroundImage:navBarImage
forBarMetrics:UIBarMetricsDefault];
Seems like the most "modern" way of achieving background/height change, although it most likely doesn't survive an orientation change. Any improvements that could be made here?
2) I replaced the default back button with the following in the viewDidLoad of the pushed view
// Set the custom back button
UIImage *buttonImage = [UIImage imageNamed:#"back.png"];
//create the button and assign the image
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:buttonImage forState:UIControlStateNormal];
//set the frame of the button to the size of the image (see note below)
button.frame = CGRectMake(0, 0, buttonImage.size.width, buttonImage.size.height);
[button addTarget:self action:#selector(back) forControlEvents:UIControlEventTouchUpInside];
// offset the back button
button.contentEdgeInsets = UIEdgeInsetsMake(10, 5, -10, -5);
//create a UIBarButtonItem with the button as a custom view
UIBarButtonItem *customBarItem = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.leftBarButtonItem = customBarItem;
I'm not very pleased with this solution because it leaves customization of the bar to the controller on top of the navigation stack. From the Apple docs it would seem like they'd prefer you to subclass the UINavigationBar altogether and replace it once and for all in the navigation controller:
You can also specify a custom UINavigationBar subclass by using the initWithNavigationBarClass:toolbarClass: method to initialize the navigation controller.
Would that be the advised route? I was NOT able to replace the default Back button of the UINavigationBar through [UIBarButtonItem appearance] as it still attempts to display text in the button, and when you remove the text, the button isn't displayed at all. Suggestions?
3) The page title should be replaceable with another view through navigationItem.titleView. Anything better out there?
Thanks!
1) You should set two images, for two UIBarMetrics (UIBarMetricsDefault and a separate image for UIBarMetricsLandscapePhone). Thus
UIImage *navBarImage = [UIImage imageNamed:#"navigation-bar.png"];
UIImage *navBarImage_Landscape = [UIImage imageNamed:#"navigation-bar-landscape.png"];
[[UINavigationBar appearance] setBackgroundImage:navBarImage
forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage_Landscape
forBarMetrics:UIBarMetricsLandscapePhone];
2) You could either subclass UINavigationBar (as you've also mentioned and that would be the default Apple way). Or if it's just the button, maybe you could hack its behaviour by passing in " " (empty text)
3) Not sure what you meant. You could setTitle of the navigation bar and it would show whatever title you want. Or you should be able to plug in another view for titleView
Note that setBackgroundImage is iOS 5.0 and later.
Related
How do I remove the horizontal padding to the left and right of custom left and right UINavigationBar items? There seems to be ~ 10 points of padding that iOS sets by default.
I'm customizing left and right navigation bar buttons (I have given up on trying to set my own backButtonItem, so I'm just using the leftBarButtonItem).
In either case (left or right), pressing these custom buttons indicates that Apple seems to preserve some padding to the left of the leftBarButtonItem, and to the right of the rightBarButtonItem; regardless of how wide I make the custom background and image properties of the UIButton I place inside the left/right bar button item as its custom view.
Since UIBarButtonItems have no "frame" I can access, I can't position them within their superview like I can normal UIViews.
Any suggestions on how to remove this default padding? See screen shot attached to see the bit I'm trying to reduce to a zero width. In the screen shot, the plus icon appears shifted to the right because I gave it an inset; but the highlighted background image, also presumably using that inset, is getting clipped on its right side).
See image at: https://skitch.com/starbaseweb/rj2e5/ios-simulator
For reference, here's how I'm creating my custom UIBarButtonItem (in this case, it's the right button):
- (UIBarButtonItem *)customAddButtonItemWithTarget:(id)target action:(SEL)action {
UIButton *customButtonView = [UIButton buttonWithType:UIButtonTypeCustom];
customButtonView.frame = CGRectMake(0.0f, 0.0f, 45.0f, 44.0f);
[customButtonView setBackgroundImage:
[UIImage imageNamed:#"bgNavBarButton-OutsideRight-Normal.png"]
forState:UIControlStateNormal];
[customButtonView setBackgroundImage:
[UIImage imageNamed:#"bgNavBarButton-OutsideRight-Highlighted.png"]
forState:UIControlStateHighlighted];
[customButtonView setImage:
[UIImage imageNamed:#"bgNavBarButton-Add-Normal.png"]
forState:UIControlStateNormal];
[customButtonView setImage:
[UIImage imageNamed:#"bgNavBarButton-Add-Highlighted.png"]
forState:UIControlStateHighlighted];
[customButtonView addTarget:target action:action
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *customButtonItem = [[[UIBarButtonItem alloc]
initWithCustomView:customButtonView] autorelease];
[customButtonView setImageEdgeInsets:UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 0.0f)];
//customButtonItem.imageInsets = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 0.0f);
return customButtonItem;
}
55As commented above, the solution I went with is based on this answer to a different, but very much related question: How to adjust UIToolBar left and right padding. It is also facilitated by (and depends on) iOS5, which allows you to set multiple buttons on the left or right side, instead of just one.
Here's an example of removing the padding to the left of a custom left button item:
UIBarButtonItem *backButtonItem // Assume this exists, filled with our custom view
// Create a negative spacer to go to the left of our custom back button,
// and pull it right to the edge:
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil action:nil];
negativeSpacer.width = -5;
// Note: We use 5 above b/c that's how many pixels of padding iOS seems to add
// Add the two buttons together on the left:
self.navigationItem.leftBarButtonItems = [NSArray
arrayWithObjects:negativeSpacer, backButtonItem, nil];
And with this, the left padding for the left bar button item in a navigation bar, is gone!
NOTE: This has worked for me in iOS5 and iOS6. Given that iOS7 is considerably different (from the public demos), those of you with the early seeds of iOS7 should test if something so unintentional, like this hack, will actually continue to work for you beyond iOS6.
I have tried this and it works:
1) Create a custom UIToolbar subclass, which does nothing in -drawRect:, and is not opaque, and has backgroundColor = [UIColor clearColor].
2) Create a custom UIBarButtonItem with the toolbar as the custom view.
3) Add your buttons to the custom toolbar.
4) In your custom toolbar override -layoutSubviews and do your own spacing.
How do I get a UISegmentedControl that is like the one in the Mail App, so that it is the same colour as UIToolbar buttons (as if both segments were in the selected state).
I want to use the segmented control for exactly the same purpose as Mail.
(on the iPad, so a grey not blue color)
This is code from Apple Sample codes... NavBar and both the images used in the code..
you shoud be able to get exact same view as mail App.
// "Segmented" control to the right
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:
[NSArray arrayWithObjects:
[UIImage imageNamed:#"up.png"],
[UIImage imageNamed:#"down.png"],
nil]];
[segmentedControl addTarget:self action:#selector(segmentAction:) forControlEvents:UIControlEventValueChanged];
segmentedControl.frame = CGRectMake(0, 0, 90, 30);
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.momentary = YES;
defaultTintColor = [segmentedControl.tintColor retain]; // keep track of this for later
UIBarButtonItem *segmentBarItem = [[UIBarButtonItem alloc] initWithCustomView:segmentedControl];
[segmentedControl release];
self.navigationItem.rightBarButtonItem = segmentBarItem;
[segmentBarItem release];
You seek the tintColor property!
When you use a UISegmentedControl you can change its tint color to any color you can dream up. So, if you added the UISegmentedControl in Interface Builder then you would style it in your - (void)viewWillAppear:(BOOL)animated method as such (assuming you had it hooked up to a #synthesized ivar:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set the tintColor to match the navigation bar
self.mySegmentedControl.tintColor = [UIColor colorWithRed:.94 green:.94 blue:.94 alpha:1];
... do whatever else in your viewWillAppear ...
}
Now obviously you will want to play with the red, green, blue, and alpha's that I've put in the sample code above, but you can literally tint the UISegmentedController any color you would like (or make it as transparent as you would like), so it's just a matter of finding the RGBA values that look perfect to you.
Remember that per Apple's docs that the default value of this property is nil (no color). UISegmentedControl uses this property only if the style of the segmented control is UISegmentedControlStyleBar.
Good luck!
I dont know exactly what you mean.. but i believe the "UISegmentedControlStyleBar" as segmentedControlStyle could it be.
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar
You can set this property in the IB too! (It's the property called "style")
The style I'm looking for is undocumented: it is style 4.
It looks like he up/down control here: http://media.mobilemeandering.com/wp-content/uploads/2010/04/ipad-mail-message-2.png
(not my image btw)
It basically makes all segments look selected, it's intended for momentary pushes, and is effectively multiple tool bar buttons pushed up together.
So it can't be set in IB but must be set in code or manually in the nib/xib file, by opening the nib as a text file.
I'm not sure I exactly understand what you're trying to do, but I'll give it a shot.
The solution is not obvious, you need to use a UISearchDisplayController in order to get a matching UISearchBar and UISegmentedControl.
See the TableSearch sample code for an example.
I have a UINavigationController where I initialize the NavigationBar as such (using MonoTouch):
NavigationBar.BarStyle = UIBarStyle.Black;
NavigationBar.TintColor = UIColor.Black;
On a UIViewController that I subsequently push onto the navigation controller, I add a button as such:
NavigationItem.RightBarButtonItem = new UIBarButtonItem(UIBarButtonSystemItem.Add, (s, e) => HandleAddItem());
However, when I touch the button, it doesn't change color/shade (i.e. animate) to signify it's been touched. Similar UIBarButtonItems added to a manually-created UINavigationBar on another view controller (plain UIVIewController) animate as I'd expect.
How can I get the navigation bar buttons to "flash" when they are on a UIViewController that has been pushed onto a UINavigationController?
I have found that once you change the UINavigationBar's barStyle and tintColor, there is a very good chance that the highlighted state for the button will be no different than the default state. I believe the best way to work around this is to use a UIBarButtonItem created from a custom view.
UIImage *bgImage = [UIImage imageNamed:#"button-normal.png"];
bgImage = [bgImage stretchableImageWithLeftCapWidth:6.0 topCapHeight:0.0];
UIImage *bgImageHighlighted = [UIImage imageNamed:#"button-highlighted.png"];
bgImageHighlighted = [bgImageHighlighted stretchableImageWithLeftCapWidth:6.0 topCapHeight:0.0];
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
[myButton setBackgroundImage:bgImage forState:UIControlStateNormal];
[myButton setBackgroundImage:bgImageHighlighted forState:UIControlStateHighlighted];
UIBarButtonItem *myItem = [[UIBarButtonItem alloc] initWithCustomView:myButton];
It's kind of a pain, especially when your button uses an image instead of a title. I would definitely recommend submitting a bug report to Apple about this.
In your particular situation, it might work better to only set the barStyle to UIBarStyleBlack and leave the tintColor property alone. I have also had luck only specifying the tintColor and leaving barStyle set to UIBarStyleDefault, as long as the tintColor is not black. In general, a tintColor of black does not work very well.
I have a UIToolbar that I've customized with my own background image. Consequently, the built-in UIBarButtonItem appearance doesn't work for me, so I'm using images that are already prepared to show in the bar. I create a custom button item with this method:
+ (UIBarButtonItem *)customWithImage:(UIImage *)image enabled:(BOOL)enabled target:(id)target action:(SEL)selector {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
//I've tried different values for the frame here, but no luck
button.frame = CGRectMake(0, 0, 44, 44);
button.enabled = enabled;
button.showsTouchWhenHighlighted = YES;
[button addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside];
[button setImage:image forState:UIControlStateNormal];
UIBarButtonItem *it = [[[UIBarButtonItem alloc] initWithCustomView:button] autorelease];
//Tried changing this, to no avail
it.width = 32.f;
return it;
I have one button on the left and one on the right and I'm trying to make it so that if you tap on the far left or far right of the UIToolbar, the corresponding button is tapped. However, with these custom buttons, the hit targets do not extend all the way to the edges of the UIToolbar, but are inset from the sides:
http://skitch.com/andpoul/d1p8g/hit-targets
Any help greatly appreciated!
UIBarButtonItem.width might be ignored if you're using a custom view (it probably just uses the width of the view).
A lame hack is to make the toolbar wider (so it sticks outside the screen) and add transparent edges to the background image to compensate. This brings the buttons closer to the edge of the screen.
An alternative is just to use a UIImageView with UIButton subviews.
I think the only way you will have to go is to make buttons wider (change image by adding some from left for one and right for another) and adjust size...
I have a UIBarButtonItem on a navigation bar. I'd like to make it arrow shaped. By that I mean I want it to be square except for a pointy side. Does anyone know how to do this?
Thanks!
I wrestled with several approaches on this one and finally figured it out using all the answers from several sources. There are a couple of tricks; this snippet will show it all (I was creating a custom rightNavButton, but you can adapt this easily for any UIBarButtonItem):
// Produces a nice "right arrow" style button that mirrors the back arrow
// automatically added by the navController
//
UIImage *buttonImage = [UIImage imageNamed:#"forwardButton.png"];
UIButton *forwardButton = [UIButton buttonWithType:UIButtonTypeCustom];
[forwardButton setBackgroundImage:buttonImage forState:UIControlStateNormal];
[forwardButton setTitle:#"Meter" forState:UIControlStateNormal];
forwardButton.font = [UIFont boldSystemFontOfSize:12];
forwardButton.frame = CGRectMake(0, 0, buttonImage.size.width, buttonImage.size.height);
[forwardButton addTarget:self action:#selector(showMeter)
forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithCustomView:forwardButton];
Note: it turns out that if you try to set the target and action directly on the button item after assigning a custom view using a UIButton that it won't take - you have to set the target and action on the button itself - apparently the navBar uses the button provided verbatim.
This is the image I was using on the opaque black navBar (you can use anything, obviously): http://raretiger.com/images/forwardbutton.png
Make a custom background. See Creating a left-arrow button (like UINavigationBar's "back" style) on a UIToolbar for how.
You may -pushNavigationItem:animated: to make the built-in back button appear, although you cannot assign custom actions to it.
For undocumented methods, you may use
UIButton* backButton = [UIButton buttonWithType:101];
to create a back "button".
You could create an UIImageView or an UIButton with the required image then use:
[[UIBarButtonItem alloc] initWithCustomView: arrowButton];
Hope this helps.
continuing on #Plamen Dragozov's idea: i noticed if you leave the uibutton as is, it would trigger a picture adjustment when touched and that is quite the opposite what a normal navigation back button does. So what I did was to uncheck "highlighted adjusts Image" and it works like a charm.
hope this helps newbies like me.
More elegant solution:
UIBarButtonItem * backButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Title" style:UIBarButtonItemStylePlain target:self action:#selector(back:)];
[backButtonItem setBackgroundImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
self.navigationItem.leftBarButtonItem = backButtonItem;
My problem was renaming the back button that appears on the pushed view controller. I found a dirty workaround and if you ignore the non-ideal animation problem, you'll get a back button with the title you want.
The trick is to change the title of the first VC in viewWillDisappear and re-set it of course in viewWillAppear
(Needless to say, by default, if there is no leftBarButtonItem set, UINavigationController will show a back button with the title of the VC that pushed the current VC)
In the VC where you push your current VC, do
-(void) viewWillDisappear:(BOOL) animated {
[super viewWillDisappear:animated];
self.title = #"Back";
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
self.title = #"Original Title";
}