I am writing an iOS app that has a 3-4 level of drill down. In the 2nd, 3rd... and so on views, I want to add a home button on the nab bar, which once pressed takes you back to the root view controller, i.e. the first screen.
I have added a button using storyboard and simply want to implement the - (void) homeButtonpressed:(id)sender function so that my first view is displayed.
Also, if this is some kind of a built in functionality for this (like there is for back button), then that would be great, as I will need to write this piece of code in all my child views.
Can somebody help me with this?
Thanks!
You can pop to rootview by using the following code
[self.navigationController popToViewController: [self.navigationController.viewControllers objectAtIndex: 0] animated: YES];
or
[self.navigationController popToRootViewControllerAnimated:YES];
Make an array of your view controllers
Pops all the view controllers on the stack except the root view controller and updates the display.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
[self.navController popToRootViewControllerAnimated:(BOOL)animated];
Related
I have an app with several views. Taking into consideration the large main view, called MyView1, it is controlled by MyView1Controller. Within MyView1, there is a button that causes a modal segue to another view, whose controller is also MyView1Controller. This modal view has a couple UILabels, and a button that terminate the modal view, bringing the user back to MainView1.
Here is the problem... Let's say in my modal view there is a UILabel called sampleLabel. While in MyView1, a button is pressed, which executes the code:
sampleLabel.text = #"changed";
Since the UILabel named sampleLabel is not on screen for MyView1, and instead is part of the modal view from MyView1, nothing happens. However, when I click on the button to view the modal view from MyView1, the UILabel hasn't changed.
This is even more puzzling since the main MyView1 and the modal view that segues off of MyView1 are controlled by the same view controller, MyView1Controller.
Can someone please tell me how I can make code that executes during the user's interaction with MyView1 change things in the modal view, so that when they press the button and segue to the modal view, the UILabel's have already been changed?
Thanks!
First of all, Apple recommends (and it makes life a lot easier) to have one view controller for each view. So you should have a second view controller. In the second view controller you would have a property called sampleLabel. In the first view controller you could use different methods to set the sampleLabel.text. I would probably create a separate sampleLabelText property in the first view controller (could be an NSString *) and set it to the text you want when the user presses a button. Then in your
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
you would get your second view controller and set its property like this:
SecondViewController *svc = [segue destinationViewController];
svc.sampleLabel.text = self.sampleLabelText;
That's it. Hope this helps.
So I have had a similar issue that I resolved through 'delegation' but not through a segue schema. Here is a link to the stackoverflow question and my answer. Delegation
Hopefully this gets you going in the right path. Instead of modally presenting a view, I push a new viewcontroller onto a navigation stack but the same answer should apply, hopefully :P
Just for the purpose of learning some particular aspects of xCode, I am creating a simple app that has 2 functional view controllers. Each contains a button that can be pressed to switch to the other. I am not using segues. I am using pointers retrieved from the app delegate.
visual illustration (click for higher resolution):
When the app loads, the root view controller presents view 1. When you click "switch to view 2," the following code causes view 2 to appear:
- (IBAction)buttonPressed:(id)sender
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[self presentViewController:appDelegate.view2 animated:YES completion:nil];
}
So far, so good.
But when you click "switch to view 1" on the second view, this same code (replacing "view2" with "view1") gives the following error:
Application tried to present modally an active controller.
So to summarize (where --> = presents), we have root --> view1 --> view2 -x-> view1
I don't care about retaining the history of who presents whom. I simply want the buttons to bring to the top (make visible) a previously displayed view controller, maintaining the state of its views.
It would be nice to know the following:
Is there a workaround that would enable me to achieve the intended behavior using presentViewController? E.g., root --> view2 --> view1
What other method(s) would be more practical for achieving the desired behavior? It/they must use the app delegate because in my real application that will be unavoidable.
Am I breaking the rules by trying to put a view controller on top without integrating into some larger architecture? E.g, is this sort of behavior supposed to be handled by navigation conrollers and pushing/popping? If so, can you explain why xCode doesn't want me to do this? Why can't I just display whatever view controller I want, without it necessarily having any relationship to other view controllers? (Maybe because that could lead to abuse of the app delegate?)
What does it really mean to "present" a view controller? What functional limitations or capabilities does it entail beyond creating pointers between presenting and presenter? What is the importance of leaving the presenting view controller "active"?
If instead make the button on view1 send the presentViewController message to the root view (which I hoped would just change the presentation chain from root --> view1 to root --> view2, leaving view1 still existing in memory but not part of this chain), I get a different error: "Attempt to present on whose view is not in the window hierarchy!" What does this mean? I can't find an explanation of window hierarchy.
Okay, I know I'm asking a lot here, but any amount of enlightenment will be greatly appreciated!!
The correct way to do this is to get the underlying rootVC to do the presenting and dismissing (as you attempt - without the dismissing part - in point 5). You can achieve this by sending a message + completion block back to the rootVC from each of view1 and view2 when you want to present the other.
When you are in view1:
- (IBAction)buttonPressed:(id)sender
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
[presentingVC presentViewController:appDelegate.view2
animated:YES
completion:nil];
}];
}
and similarly for view2. Take care that you need this line:
UIViewController* presentingVC = self.presentingViewController;
as you can't refer to 'self.presentingViewController' inside the completion block as it's controller has been dismissed at this point.
I think that answers points 1 and 2.
To answer point 3 "Why can't I just display whatever view controller I want, without it necessarily having any relationship to other view controllers?" - well you can (via the rootViewController property of the window), but then you are going to have to implement navigation and manage your viewController pointers, which means you will end up creating a controller of some sort. Apple is helping you here by providing you with a few which cover most needs.
As regards your point 4 - the presenting of a viewController is controlled by the presenting VC, which is why you want to keep that one 'active'. When you send this message:
[self dismissViewControllerAnimated:completion:], self just reroutes the messge to it's presentingViewController. If you get rid of your presentingViewController your dismiss method will break.
Point 5 is answered above. You need to dismiss the topmost view first before asking an underlying view to present. Note that view1 is "still in memory" but only because you have retained a pointer to it in your app delegate.
update
As you are trying to get this to work with an initial launch-straight-to-view1, you could make a BOOL launched property and check/set it from your rootViewController's viewDidAppear:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.launched) {
self.launched = TRUE;
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[self presentViewController:appDelegate.view1
animated:YES
completion:nil];
}
}
Let me try to tackle your points one by one.
1) No, you shouldn't do this all with presentViewController.
2) If you want to do root --> view1 --> view2 --> view1, then you don't do that all with presentViewController. To go from view1 back to view2 you should use dismissViewControllerAnimated:completion.
3) The view controllers do have a relationship when you use presentViewController:animated:. The presenting controller has a pointer to the one it presents, and the presented one has a pointer to the one that presented it. So, you're getting these relationships whether you want it or not. There is a way to display whatever controller you want with no relationship between them -- just reset the window's root view controller. The old view controller will be deallocated (if you don't keep a strong pointer to it), and the new one becomes the window's root view controller.
4) Presenting a view controller makes that controller a modal view controller -- it takes over the whole screen and is intended to be used as an interruption in the flow of the app. You really shouldn't use them extensively to go from one controller to another (and especially not for going "backwards" to previous controllers). Because of the way it's supposed to be used, you normally want to go back to the controller that presented it, so that's why it's kept "active" (in the sense that it's not deallocated).
5) You get that error because root's view is not on screen, view1's is. You need to present a view controller from the controller on screen.
My UINavigationController contains a UIToolBar with 3 UIBarBottomItems - It has all been drag/drop designed in the storyboard. I want this UIToolbar to be shared on all my views. I have therefore set checked the "shows toolbar". But when I run it the UIToolBar is empty in all of my views. What could be the reason for this ?
I realize this is an old thread but I've just been struggling with this for a couple hours and finally figured out what was wrong. It was something simple so thought I would share. I was calling [self.navigationController setToolbarHidden:NO]; in viewDidLoad. The problem was that viewDidLoad is called before the view controller is pushed onto the navigationController so self.navigationController is nil. I moved the code to viewWillAppear: method and it worked.
UINavigationController have default toolbar. which you can use. you can use following code
[self.navigationController setToolbarHidden:NO];
in the topmost view controller and
[self setToolbarItems:items];
in all your view controllers, where items is an NSArray of that view controller's toolbar items.
select your initial view controller in the storyboard and embed it in navigation controller.
now all your pages should have the navigation bar.. if u manually dragged dropped the previous bars when u run the program it'll show both...
You'll have to remove the old ones and then modify the new one as required.
I've set up a really simple project using storyboards including two views as shown here: http://i.stack.imgur.com/iRx21.png. The navigation can be done by either selecting a cell in the custom table view or hitting the back button labelled with "<<". Everything works fine except the following:
when I switch between the views, every time an instantiation happens. The profiling shows an increasing number of view objects. I would like to keep only one of each view and instantiation should be happen only once. What am I doing wrong? (I'm using ARC.)
Thanks in advance!
You should not link your back button to the parent view controller. This is what causes the new instantiation.
The way to go is to embed the table view into UINavigationController (in IB, choose Editor -> Imbed In -> Navigation Controller. Then change your segue to a Push segue. You can of course hide the navigation bar etc. to make things look exactly as you like. Then, link the back button to the controller with an IBAction and in the handler do a simple
[self.navigationController popViewControllerAnimated:YES];
This would be the appropriate logic of what you are doing. Of course, you can also push the web view modally and then handle the button click with
[self dismissModalViewControllerAnimated:YES];
I have used UITabbar control to navigate to the top level pages of my app. Each top level page navigates to 5 other pages using command buttons to drill deeper. When the user selects the next tabbar item, instead of navigating to the root, they end up on the page they were last reading in the hierarchy. I am using XCode 4 and Tab Bars have been created using IB.
How can I change this behavior so that the tab bar button always navigates to the top level page? Your help would be very much appreciated since I have spent many days trying to resolve this issue.
I believe this will work if your tabs have UINavigationController at their root
//somewhere in the initialization process
tabBarController.delegate = self
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
UINavigationController *navController = (UINavigationController *)viewController;
if([navController.viewControllers count]>0)
[navController popToRootViewControllerAnimated:NO];
}
I have implemented UITabBar with each button loading a separate view controller and each of those view controllers having a navigation controller as a drill-down interface. When each button on the tab bar is clicked, the UIViewController is loaded and it is responsible for handling the drill down.
This sounds like what you want to do. Since I'm not sure what you mean by "page", I don't know how you are implementing the change that happens when a button is tapped on the tab bar. It sounds like you have one UIViewController and that's not quite how the UITabBar was designed to function. I could be wrong, so I think a little more explanation is needed.
If you are implementing with multiple UIViewControllers, You may want to think about whether a user is going to want to remain buried in the drill down even on changing tabs. Not sure what you App does, but this may be desired behavior.