I have a view controller which contains a table view, and which is wrapped within a navigation controller, i.e. in the app delegate these two are created and set as:
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.myViewController];
self.window.rootViewController = navController;
If the user clicks on a row within a table then another view controller is created and pushed to the navigation controller's stack:
[self.navigationController pushViewController:webPageController animated:YES];
The webPageController loads and reads local files. If a file is missing I want to abort the loading of the webPageController and the displaying of its view and have the table view displayed.
How should I achieve this?
If the webPageController detects a problem I've tried experimenting with it calling various things such as:
[self.navigationController popViewControllerAnimated:YES];
or
[self.navigationController.navigationBar popNavigationItemAnimated:YES];
To pop itself off the navigation stack, however these aren't working, is it wrong for a navigation controller to attempt to pop itself like this? What is the canonical way of implementing this?
Thanks
That should be fine. Where are you calling popViewControllerAnimated:? If you're calling before viewDidAppear, you'll likely run into problems. ViewControllers need to finish the appearing and disappearing before they can make any kind of pop or push to their stack. If you do it before, you get really weird results. The most common symptom of this is it doesn't work. Often buttons inside it will get messed up as well.
Related
I have a root view open up on a view controller, however once there I am unable to push to new View Controllers. The actions were working whenever I would navigate to this page, but no longer do once I made it my root view. Here is the action I have, which is called but fails at pushing to the new view.
-(IBAction)pushToSettings:(id)sender{
NSLog(#"Problem");
SettingsView *viewController = [[SettingsView alloc] init];
[self.navigationController pushViewController:viewController animated:NO];
}
Is there anything wrong which is causing this action not to push to the new view?
The problem is that you are not in a navigation interface (i.e. this view controller is now not the child of a UINavigationController). There is thus no navigationController, so it is nil and nothing happens when you send the pushViewController message to it.
I'm currently trying to use push segue to navigate between two views. This works fine elsewhere in my app, no problems. However, in this particular location, I'm presented with the following error:
Terminating app due to uncaught exception 'NSGenericException',
reason: 'Push segues can only be used when the source controller is
managed by an instance of UINavigationController.
Now here's the thing, i know exactly what this error means, and exactly how to fix it.
Editor -> Embed in -> Navigation Controller on the view controller i'm trying to push from.
The thing is, I've done that already and the error persists. Any ideas?
For what it's worth, the navigation bar doesn't even appear in the view that was embedded inside the nav controller.
Here is the current setup
I have a ViewController on the storyboard that is setup to inherit from UIViewController. That controller is embedded inside a UINavigation controller via the above method.
On this view controller view, there are two buttons. Inside IB I have dragged a push segue from each of those buttons to the respective view controllers I would like to present.
I've also tried doing the segue in code via the following:
- (IBAction)btnTerms:(id)sender {
UIViewController *termsVC = [STORYBOARD instantiateViewControllerWithIdentifier:#"TermsOfServicesViewController"];
[self.navigationController pushViewController:termsVC animated:YES];
}
In the above case, nothing happens at all. No crashes or anything. Debugger breakpoints confirm that the method is being hit, though.
Update as per Phillip's question
UINavigationController *nav = self.navigationController;
[self.navigationController pushViewController:termsVC animated:YES];
- (IBAction) btnSignUpCLicked:(UIButton *)sender {
[self presentViewController:[STORYBOARD instantiateViewControllerWithIdentifier:#"SignUpViewController"] animated:YES completion:nil];
}
From the comments:
Embedding a view controller inside a navigation controller will cause the embedded controller to load when its navigation controller does. The reverse is not implied (, which is reasonable because there might be a case where the embedded controller would be also useful standalone).
I'm currently working on an app that builds a stack of controllers depending on the user.
Basically, I have a UIViewController that has a UIButton leading to another UIView Controller; that has a button leading to another view controller and so on. The view controllers are pushed so that when the user always press the button, I get a stack of multiple view controllers. The views are popped whenever the user wants to go back to the previous view controller.
Everything is working well (push and pop). However, at random instances, the app would crash. I noticed that it happens when there are already a large amount of views pushed, and I suspect that it can be a memory issue.
My question is, other than pushing the view controllers, is there an alternative so that I can avoid stacked views? Could it also be that the crash is not because of the stacked views but because I'm just missing something out? There is no error presented in the logs so I can't find out what's happening and I'm also new to iOS development.
Thank you very much!
Edit 1: There is no error in the logs but when the app crashes, there is this message:
Thread 1: EXC_BAD_ACCESS(code = 1, address = 0xd000000c)
Edit 2: This is how I am pushing the controller:
CustomController *custom = [self.storyboard instantiateViewControllerWithIdentifier:#"Custom"];
[self.navigationController pushViewController:custom animated:YES];
And this is how I popped it when the back button is pressed:
[self.navigationController popViewControllerAnimated:YES];
Edit 3: After enabling zombie objects in the scheme, I started to get this messages after multiple push and pop:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Unbalanced calls to begin/end appearance transitions for
Do those messages say that the problem is actually on pushing the controller with animations? Thanks everyone!
Edit 4: I'll try to revise the question to make it more descriptive
This is my setup:
Controller A displays icons that corresponds to different places. You can click on the icon to push Controller B and display details for Location A.
Controller B displays information about Location A, with a button to show Controller A that now displays icons close to location of Location A. Now, you can again click an icon, say for Location B, and display details and so on.
When the user presses the back button, it should display the previous view controller. This is why I used push and pop. Is there a better way to handle this process. Thanks again!
My suggestion is: check if Zombie Objects is enabled and use the instrument "Allocations" to see if your application have, in fact, memory issues. With the informations provided by these tools, you can figure out what is happening with your application and work on it.
Tell me if you need help.
Good luck!
When push or pop, you should turn off animation. I think this causes crash when animation does not finish.
Push: self.navigationController pushViewController:custom animated:NO];
Pop: [self.navigationController popViewControllerAnimated:NO];
My guess is that you push multiple view controllers with animations - this may be a root cause of error. If you push more than one view controller you should animate only LAST push, say:
VC1 *vc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC1"];
[self.navigationController pushViewController:vc1 animated:NO];
VC2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2"];
[self.navigationController pushViewController:vc1 animated:NO];
VC3 *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3"];
[self.navigationController pushViewController:vc1 animated:YES];
However, i hardly imagine the situation where the multiple push would be necessary - i think it always leads to bad UX.
I have an application that has a login page. It then moves to a navigation controller that has a collection view as its root view controller. When the app starts and there is only one item in the collection, I want to have the navigation controller automatically push to that item and allow the user to use 'back' to view the collection. This is the same behavior that is in 'Notes' from Apple.
The idea is to allow the user to immediately start to use the app and only 'discover' the need for the collection view after using the app for some time.
I am using IB, storyboards, and segues for my view transitions.
If I programmatically have the root view controller do a performSegue in its viewWillLoad: I get an error about causing a transition while a transition is still in process.
If I move the code calling performSegue into the didLoad, then the user sees a double transition.
A navigation controller usually manages the underlying stack itself. That is, you pass it an initial view controller, and then through segues or pushViewController:animated: the stack is altered. However, there is nothing stopping you from manually altering the stack. In prepareForSegue:sender:, you can check the number of items, and if it's 1, do something like this:
UIViewController *singleItemViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SingleItem"];
NSMutableArray *viewControllers = [sender.destinationViewController.viewControllers mutableCopy];
[viewControllers addObject:singleItemViewController];
sender.destinationViewController.viewControllers = viewControllers;
Now instead of starting at the first view controller of the stack, you start at the second one, with a back button to navigate back.
Actually, I found that the following worked the best for me.
if (/* reason to change back list*/) {
UIViewController * rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"backViewController"];
NSArray * viewControllers = #[rootViewController, self];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
setViewControllers seems to be the best way to manipulate the stack.
I'm starting to learn iOS development and am attempting to use UINavigationController as my window's root view. All is working well but I need some advice on how to structure my app. I read the docs and some questions on here too.
Since the navigation controller is managing all my other content view controllers, then all of these view controllers need a way to send messages to the navigation controller. Right? So, I've thought about making a singleton navigation controller that any other view controller can call on to push new view controllers on it. Or, if each view controller has a reference to the navigation controller then they can push/pop easily as well. This is the part I'm not sure about.
Also, for having buttons and actions I have been setting the target as the navigation controller and from there it can handle it correctly and push or pop on it's own. I did subclass UINavigationController for this. And I have my view controllers as references in it. However I ran into an issue where my UITableViewController was handling a selection of a row and I need to push a new view controller on top, but how do I get the reference to the navigation controller?
I hope that makes sense, and any advice on how to structure this would be very appreciated.
Thanks!
I think you're overthinking this. View controllers have a navigationController property built in allowing you to reference the navigation controller. That being said, pushing to a new view controller from within a view controller that is embedded in a navigation controller is as easy as:
UIViewController *myNewViewController = [[UIViewController alloc] init];
[self.navigationController pushViewController:myNewViewController animated:YES];
According to your requirement you need to write like this :-
UIViewController *myNewViewController =
[[[UIViewController alloc]
initWithNibName:#"yourNibName"]
bundle:nil]];
[self.navigationController
pushViewController:myNewViewController
animated:YES];
This will push one controller on the bottom of the stack.