I have an application which has a number of views which are navigated to via a number of custom UIButtons from my main view. The main view is named iBMRViewController and features some welcome graphics in the form of PNG images dropped in through interface builder. It also features 6 custom UIButtons which I have created through code using the following;
// This is the code which creates, and defines the properties of the 'Warning' button on the main view.
UIButton *warningButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
warningButton.frame = CGRectMake(225.0, 270.0, 60.0, 60.0);
[warningButton setTitle:#"" forState:UIControlStateNormal];
warningButton.backgroundColor = [UIColor clearColor];
[warningButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal ];
UIImage *warningButtonImageNormal = [UIImage imageNamed:#"Warning.png"];
UIImage *warningStretchableButtonImageNormal = [warningButtonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[warningButton setBackgroundImage:warningStretchableButtonImageNormal forState:UIControlStateNormal];
UIImage *warningButtonImagePressed = [UIImage imageNamed:#"whiteButton.png"];
UIImage *warningStretchableButtonImagePressed = [warningButtonImagePressed stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[warningButton setBackgroundImage:warningStretchableButtonImagePressed forState:UIControlStateHighlighted];
[warningButton addTarget:self action:#selector(warningButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:warningButton];
Now, this all works fine and the buttons function perfectly, obviously I also have Actions set up for them which work perfectly. On each page I have a UINavigationBar with a UINavigationItem on set up through interface builder and set to take me back to my main view using the following code;
//This is the code which opens up the new view when 'Begin' button is tapped.
-(IBAction)beginHomeButtonAction:(id)sender {
iBMRViewController *controller = [[BeginView alloc] initWithNibName:#"iBMRViewController" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
This also works, however, when it takes me back to the 'iBMRViewController' it ONLY displays what was set up via the interface builder xib file (i.e. the welcome png files). It does not display the buttons I added through code.
Can anyone give me an idea of where I have gone wrong? Would be extremely appreciated.
Thanks
In fact, the beginHomeButtonAction doesn't "take you back to the main view". It creates a new view controller, with a new view, and presents that. It's the same kind of controller class, but not the same instance of that class.
To dismiss the view, and in fact take the user back to the main view, you have to dismiss the view that you presented from the main view. How to do this depends on how you presented then, but you could try popViewControllerAnimated or dismissModalViewControllerAnimated. (google is your friend)
Related
After searching through several question on StackOverflow I've found out that there is only 1 major project for creating custom UITabBar called BCTabBarController. The description to it says:
There are several problems with using the standard UITabBarController
including:
It is too tall, especially in landscape mode
The height doesn't match the UIToolbar
It cannot be customized without using private APIs
Nevertheless, I've found this strange project on GitHub with the tutorial here that uses standard UITabBarController in its implementation with UIButtons for each tab and it's working (strangely enough, but it does).
I was wondering, if this is wrong to create your custom UITabBarController with UIButtons instead of tabs and what would it result into? The implementation of this looks like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self hideTabBar];
[self addCustomElements];
}
- (void)hideTabBar
{
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
view.hidden = YES;
break;
}
}
}
-(void)addCustomElements
{
// Initialise our two images
UIImage *btnImage = [UIImage imageNamed:#"NavBar_01.png"];
UIImage *btnImageSelected = [UIImage imageNamed:#"NavBar_01_s.png"];
self.btn1 = [UIButton buttonWithType:UIButtonTypeCustom]; //Setup the button
btn1.frame = CGRectMake(0, 430, 80, 50); // Set the frame (size and position) of the button)
[btn1 setBackgroundImage:btnImage forState:UIControlStateNormal]; // Set the image for the normal state of the button
[btn1 setBackgroundImage:btnImageSelected forState:UIControlStateSelected]; // Set the image for the selected state of the button
btn1.backgroundColor = [UIColor yellowColor];
[btn1 setTag:0]; // Assign the button a "tag" so when our "click" event is called we know which button was pressed.
[btn1 setSelected:true]; // Set this button as selected (we will select the others to false as we only want Tab 1 to be selected initially
In my project I will be using iOS 5.1 and up and no Storyboards or XIBs. Thanks!
Since iOS 5.0, it is no longer a problem to create your own UITabBarController using a line of UIButtons at the bottom of the screen.
In previous versions of the iOS SDK, it was a bit risky as you had to manage the forwarding of the viewWill/viewDidmethods by yourself.
Have a look at the UIViewController Class Reference, section Implementing a Container View Controller, you will find all you need there : UIViewController Class Reference
There is also a featured article explaining exactly what you need : Creating Custom Container View Controllers
Hope this will help,
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.
I've got two viewControllers who each need a login button in the top right corner of the navigation bar.
In viewController1#viewDidLoad, I set up the rightBarButtonItem like so (abbreviated):
// set up the login button on the right
UIButton *loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *loginImage = [UIImage imageNamed:#"btn_login.png"];
[loginButton setBackgroundImage:loginImage forState:UIControlStateNormal];
[loginButton setFrame:CGRectMake(0, 0, loginImage.size.width, loginImage.size.height)];
[loginButton setTag:1111111];
UIBarButtonItem *loginItem = [[UIBarButtonItem alloc] initWithCustomView:loginButton];
self.navigationItem.rightBarButtonItem = loginItem;
[loginItem release];
I tag it so that in viewWillAppear, I can use viewWithTag:1111111 to figure out if it needs to be hidden or visible, based on whether or not the user is logged in. Simple.
((UIButton *)[self.navigationController.view viewWithTag:LOGIN_BUTTON_TAG]).hidden = true;
When viewController2 gets pushed onto the stack, I basically run the same code to set up my rightBarButtonItem, but I give it a different tag (i.e. 222222).
In viewController2#viewWillAppear, when I look for the viewWithTag:222222, it comes back as null, and so I can't hide/show it.
I noticed though that if I use the same tag as I used in viewController1 (1111111), I can get to it.
Why is this? My tags are actually set up at the top of the file as constants, so it seems inelegant to copy the random number from vc1 into vc2 just so I can get this to work. Instead, I'd like to understand why vc2's tag isn't getting applied to the rightBarButtonItem, and why vc1's tag is still preserved even though I'm in a different viewController.
The easiest most scalable solution is to avoid using viewWithTag: like the plague.
Make the UIBarButtonItem an ivar and then you have instant access to it without any ambiguities.
Replace
[loginButton setTag:1111111];
with
self.loginButton = loginButton;
Then to retrieve do this
self.loginButton;
instead of
(UIButton *)[[[self.navigationController visibleViewController] view] viewWithTag:LOGIN_BUTTON_TAG];
I know which one looks more elegant and robust to me
If I understand correctly what you are doing, the first thing I would try is to check, before this line, in both viewcontrollers:
((UIButton *)[self.navigationController.view viewWithTag:LOGIN_BUTTON_TAG]).hidden = true;
to put a
NSLog("View is %#", self.navigationController.view);
to check if you are sending viewWithTag to the correct view.
Not sure about this, but Are you trying to access to a view, controller by an UIViewcontroller, loaded inside an UINavigationcontroller?
In this case I would use:
(UIButton *)[[[self.navigationController visibleViewController] view] viewWithTag:LOGIN_BUTTON_TAG];
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 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";
}