Strange UINavigationController issue - ios

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).

Related

Present a view controller from a container view

I had built a UIViewController with a container a view that embeds another UIViewController(I will call it subViewController).
I want the subViewController to present anther instance of it self within its container view canvas (not for all screen).
I tried this using prepareForSegue method
with this method
-(void)showFurtherReadingDetails
{
[self performSegueWithIdentifier:#"ShowArticleDetails" sender:self];
}
Note : the showFurtherReadingDetails method is a delegate method for subVC over, initialized in the supperVC .
but I faced this issue:
'NSInternalInconsistencyException', reason: 'There are unexpected
subviews in the container view. Perhaps the embed segue has already
fired once or a subview was added programmatically?'.
Please see The attached image
If your UIViewController is not embedded with UINavigationController then you can not perform Push.
What you have to do is, embed-in navigation controller with your subVC and then push new view-controller from subVC and it will be in container view and will not take the full screen.
For your convenience, I attached the screenshot of the storyboard so you can get the better understanding. Hope it will help.

Two Modal VC are displayed simultaneously ios

For some reason, when I try to open a view controller via modal segue, it opens up two of the same type. Why is this happening?
Warning: Attempt to present <ModalViewController: 0x7fa062c5edd0>
on <HomeViewController: 0x7fa062e16e40> which is already presenting
<ModalViewController: 0x7fa062fb9780>
This is causing problems because I try to use delegates, but my main view controller never gets the correct delegate.
The issue occurs when I click the the button which triggers showModalView
HomeViewController
- (IBAction)showModalView:(UIButton *)sender {
ModalViewController *modalView = [[ModalViewController alloc] init];
[self presentViewController:modalView animated:YES completion:nil];
}
I tried this solution here and here and a dozen other ones, but none seem to work for me.
Why is this happening?
The problem you're having, is because you've connected a segue to the button, and are also presenting the controller in code; you should be doing one or the other. When you removed the segue, you got a black screen because you're using alloc init to create your controller. If you made the controller in a storyboard, then you should use instantiateViewControllerWithIdentifier: instead.
However, the easier way would be to leave the segue connected to the button, and delete the code you have in the button's action method. The button doesn't need an action method, if you have it hooked directly to a segue. All of this is covered in Apple's document, "View Controller Programming Guide for iOS". You should read it.

How do I emulate 'Notes' from Apple and start the app with a navigation controller with a 'back' view?

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.

After using presentViewController existing segues can't be found

I am returning to my login view using the code below. The view loads correctly and everything looks fine. All buttons work etc.
JALoginViewController *loginVC = [[JALoginViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:loginVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
However, if a user tries to log in again, the segue that takes them to the next scene can't be found.
I'm using performSegueWithIdentifier if the users login credentials are correct, like this:
[self performSegueWithIdentifier:#"loginSegue" sender:self];
This is the error I receive:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<JALoginViewController: 0x8d614b0>) has no segue with identifier 'loginSegue''
I've done lots of searching on Google and through the docs for the solution to this, the closet I've got (at least I think) is this question. The explain and solution sound like they could be correct and relevant, but I can't put them into practice.
Documents I've read and tried:
initWithRootViewController
popToRootViewController - The current root view controller is for a tab bar - not the login scene I need so as far as I'm aware I can't use this.
popViewControl
pushViewControl - This works to an effect, I don't think it is the correct way though. I don't want there to be navigation bar and I don't want my tab bar to appear when the user returns to the login scene.
I've tried various methods with limited / no effect. At this stage any help would be greatly appreciated.
Please let me know if I haven't provided enough information.
Thanks
JA
Edit - Zoomed out image of storyboard
![Zoomed out image of Storyboard][1]
On the basis of the screen snapshot of the revised question, from your rightmost red-highlighted scene, you should be able to:
[self.tabBarController dismissViewControllerAnimated:YES completion:nil];
and you'll be back at that initial screen (I'm assuming you did modal segue from initial screen to your tab bar controller).
Original answer:
If you want to manually push to a view controller, rather than creating it via alloc/init, you should use
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsSceneStoryboardIdHere"];
or, if that view controller was the "initial" scene (the one with the simple arrow coming in from the left), you could use
UIViewController *controller = [self.storyboard instantiateInitialViewController];
And you shouldn't be manually creating the navigation controller, either. If the loginVC needs a navigation controller, you should embed that scene in a navigation controller right in the Interface Builder, then give that new navigation controller its own unique storyboard identifier, and then you can
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"navControllerSceneStoryboardIdHere"];
[self presentViewController:controller animated:YES completion:nil];
I must confess that I'm worried by this whole "return to login via pushViewController" construct. I assume you know that you're not "returning" to it, but creating a new copy of it. If you push/modal from A to B and then B to C and then C to A, you're holding 4 views and their controllers in memory, two copies of A and one of B and one of C (which is, obviously, not good). I just wanted to make sure you don't have a circular set of segues and/or push/presentViewController references.
If the login is the initial scene in your app and if you've been doing only push segues (no modal segues along the way), you can do a:
[self.navigationController popToRootViewControllerAnimated:YES];
That will take you to the top level view controller, and it will pop off and release all of the intervening scenes.
If you're using iOS 6, you can avail yourself of the unwind segue, which can achieve the same functionality, but it doesn't care whether the preceding segues were pushes or modals.
There are lots of ways of skinning the cat, but generally doing a new presentViewController to the first scene in your storyboard is a very bad idea.

How should a view controller abort loading / dismiss itself?

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.

Resources