I posted a question earlier today regarding reacting to the receipt of a push notification, whereby I needed to drill down from one view to another. This question was answered, however I have now moved on to another requirement for reacting to push notifications, whereby I need to drill down to a third level in the navigation stack.
Below is a mockup of my view hierarchy, where on receipt of the push notification I want to drill down to the view titles "Third View".
From the answer to my previous question, I am able to drill down to "Second View", however when I try to perform the segue to push "Third View" on to the stack, I get the same type of error as per my previous question:
Terminating app due to uncaught exception 'NSGenericException', reason: 'Could not find a navigation controller for segue 'Admin'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
In my AppDelegate, when I receive the notification I select the required tab then:
UINavigationController *navController = (UINavigationController*)[_tabBarController selectedViewController];
NHProfileViewController *profileViewController = navController.viewControllers[0];
[profileViewController displayAdminWebViewForPushNotification];
Then in profileViewController (First View) displayAdminWebViewForPushNotification:
_displayAdminForPushNotification = YES;
[self performSegueWithIdentifier:#"Settings" sender:self];
where "Settings" is "Second View" in the image above.
Then in prepareForSegue:
if ([segue.identifier isEqualToString:#"Settings"])
{
NHSettingsTableViewController *settingsViewController = segue.destinationViewController;
if (_displayAdminForPushNotification)
{
[settingsViewController displayAdmin];
_displayAdminForPushNotification = NO;
}
}
Up to this point all is fine, however as soon as I hit [settingsViewController displayAdmin]:
- (void)displayAdmin
{
[self performSegueWithIdentifier:#"Admin" sender:self];
}
it crashes. I have done some debugging and at this point settingsViewController does not have a navigation controller (hence the crash/error message). This I don't understand as "First View" is embedded in a navigation controller.
Your performing the third segue to early -- the second segue isn't even done so it has no navigationController.
delay it by using this method to perform action after segue:
How to execute some code after a segue is done?
or use viewWillAppear!
Related
I have a UIViewController called "Yellow" in my storyboard that I'm adding like this:
_detailVC = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
_detailVC.location=cell.location;
self.incomingView = _detailVC.view;
[self addChildViewController:_detailVC];
[self.view addSubview:self.incomingView];
self.incomingView.alpha = 0;
[_detailVC didMoveToParentViewController:self];
I need this UIViewController to exist within a UINavigationController as I'm using a push segue and it is giving
me the following error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Could not find a navigation controller for segue 'pushDetailVC'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
If I go to the view for the "Yellow" view controller, and "embed in Navigation Controller", I still
get the same problem.
How would I embed this UIViewController in a UINavigationController so that the push segue works
correctly?
When you instantiate a view controller from the storyboard, you don't automatically get any controller that it might be embedded in. You're telling the system to give you a DetailVC, and that's what it gives you. If you want the navigation controller, then you need to instantiate it, and it will automatically instantiate its rootViewController (which is Yellow) because its hooked up with a relationship segue.
UINavigationController *nav = [self.storyboard instantiateViewControllerWithIdentifier:#"Nav"];
_detailVC = (DetailVC *)nav.topViewController; // replace DetailVC with whatever your class name is
_detailVC.location=cell.location;
To use a push segue, the UIViewController you are pushing from must be within the same UINavigationController as the one you are pushing to. If you don't want that top bar to be displayed on the view you are pushing from, then you should consider a different type of segue to make the Yellow view appear (though the yellow may still be within a Navigation Controller if it has its own hierarchy to take care of).
I am working on an app whose main UI is based on a tab bar controller.
In one of the tabs I have a collection view, which drills down to a detail view via a navigation controller.
What I am trying to do is upon receipt of a push notification I would like to select this specific tab, fetch the latest data from the server, find the particular item to display, then push the detail view on to the screen to display said item.
My problem is I get the following message after collectionView:didSelectItemAtIndexPath:
Terminating app due to uncaught exception 'NSGenericException',
reason: 'Could not find a navigation controller for segue
'FavouriteItem'. Push segues can only be used when the source
controller is managed by an instance of UINavigationController.'
Here is what I am doing so far:
App Delegate application:didReceiveRemoteNotification:
[self selectFavouritesTab];
NHFavouritesViewController *favouritesViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"Favourites"];
[favouritesViewController displayFavouriteForPushNotificationWithId:favouriteId];
From FavouritesViewController - After fetching the latest favourites, I send a message to displayFavouriteItemWithId:
- (void)displayFavouriteItemWithFavouriteId:(NSNumber*)favouriteId
{
NSArray* results = [_collectionViewData filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF.favouriteId == %#", favouriteId]];
NSInteger row = [_collectionViewData indexOfObject:[results lastObject]];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
[[self collectionView] selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
[self.collectionView.delegate collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"FavouriteItem" sender:self];
}
And it is at this point it crashes. I understand what the crash message is saying, however what I don't know is how to place NHFavouritesViewController inside a navigation controller (which is embedded inside one in the storyboard) when I respond to the push notification in the app delegate?
You can wrap a view controller in a standard navigation controller with:
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:favouritesViewController];
But I can't see from your code above how favouritesViewController is presented in the tabBarController. If you are doing it in a storyboard, then just drag in a blank navigation controller, hook the relevant tab of your tabBarController to the navigation controller (Ctrl-drag, then select "Relationship segue: viewControllers", and then hook from the navigation controller to your FavouritesViewController (likewise).
EDIT:
If that is already done in the storyboard, then you need to amend your code to pickup the existing version of NHFavouritesViewController, instead of instantiating new. Something like (assuming you have a reference to your Tab Bar Controller in self.tabBarController, and the favouritesViewController is in the tab with index favouritesTab (I assume you can get these, since you already have a method to select the tab):
UINavigationController *navController = (UINavigationController *)self.tabBarController.viewControllers[favouritesTab];
NHFavouritesViewController *favouritesViewController = (NHFavouritesViewController *) navController.rootViewController;
The problem that you're having is that you are not instantiating the navigation controller.
By loading the favourites view using that method you are literally only creating that one view controller.
So then when you are telling it to push it can't because you didn't instantiate the navigation controller from the storyboard.
The chances are that the navigation controller already exists so you need to get hold of that instead of creating new controllers.
I'm on a mobile right now so can't answer fully but let me know if you're still struggling and I'll see if I can ad done code. Will prob need to see more code first though.
I've been looking this up for a while now, it might have a simple answer:
According to the Apple docs, past ios6, we can subclass UINavigationController. How do we perform a segue from identifier when it prevents anything that isn't a UINavigationController. Mainly:
uncaught exception 'NSGenericException', reason: 'Could not find a
navigation controllerfor segue 'profileSegue'. Push segues can only
be used when the source controller is managed by an instance of
UINavigationController.
I'm using JaSidePanels and my center panel (navigation) needed to be subclasses for a delegate as there is a menu on the left panel that I want to switch the views when clicked.
#interface CenterViewController : UINavigationController <MenuDelegate>
Basically, since this object is a CenterViewController at runtime, is there a way to cast it to its superclass? I've tried [self superclass] but that didn't work (same error).
I need to call this code in the CenterViewController. Is it possible to move it to UINavigationController?
- (void)viewDidLoad
{
RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
MenuViewController *leftViewController = (MenuViewController *)rootViewController.leftPanel;
// Store a reference to the center view controller in the left view controller's delegate property
leftViewController.menuDelegate = self;
[super viewDidLoad];
}
- (void) MenuItemSelected: (NSString*) item
{
if ([item isEqualToString:#"Home"]) {
//These would throw the error since we're not technically a "UINavigationController"
//[self performSegueWithIdentifier: #"mapViewController" sender: nil];
} else if ([item isEqualToString:#"Profile"]) {
//[self performSegueWithIdentifier: #"profileSegue" sender: self];
}
}
EDIT:
More information with pictures.
I'm curious as to how navigation controllers should work with side panels. I've looked at many other plugins for sidepanels and here is an example. Even though it works, why does it have 2 nav controllers?
Here is our current setup:
So basically, am I thinking about this wrong in the sense that I want to push a new VC to the existing NavVC? Would it be better to push a new NavVC when a menu button is pressed? What would happen when we go into a subview from the Maps view. Would the menu be accessible via sliding?
If you look carefully at the message you will see that your problem isn't caused by subclassing UINavigationController it is because you are executing the segue against your (subclassed) UINavigationController. Push segues are executed against a UIViewController that is embedded in or managed by a UINavigationController, with the system then finding the managing UINavigationController via the view controller's navigationController property in order to execute the push.
The message you have received says
...Push segues can only
be used when the source controller is managed by an instance of
UINavigationController
In your case the source controller is an instance of UINavigationController, it isn't managed by a UINavigationController.
You haven't said exactly how your app navigation works, but I have a suspicion that a UINavigationController isn't the right tool to use anyway. If you are using the side menus to allow the user to select the central content in a random way (i.e. the user could select the first option then the fifth and then go back to the first) then you should probably just have a central view into which you present the selected view. Pushing views onto a UINavigationController will end up with a large 'stack' of views unless you pop the current view controller before pushing the new one, which is even more complicated and not the visual effect you are looking for.
You can still achieve the 'push' style transition but you will need to use a custom segue. If it were me I would probably push from the left if the user selected a menu item that was closer to the top than the current option and from the right if the new item was closer to the bottom than the current, but again I am making assumptions on how your app navigation works.
UPDATE
Yes, I think you are on the right track with the section of your updated question. Navigation controllers are for navigating a series of related views in a hierarchical manner - think of the Settings app - you select "general" or "wall paper" or whatever - each of these then has a series of views that you can navigate through; up and down a stack.
In your app it looks like home, profile and settings should each be navigation controllers. Your root view would then just be a view. Your menu would select which view controller to present in the root view - this is like a tab bar controller, except your menu takes the place of a tab bar.
You can allocate your home, profile & settings view controllers in your appDelegate and store them to properties of the appDelegate. Then you can use something like this:
- (void) MenuItemSelected: (NSString*) item
{
myappDelegate *app=(myappDelegate *)[UIApplication sharedApplication].delegate;
[delegate.currentViewController removeFromParentViewController];
UIViewController *newController;
if ([item isEqualToString:#"Home"]) {
newController=app.homeViewController;
} else if ([item isEqualToString:#"Profile"]) {
newController=app.profileViewController;
}
if (app.currentViewController != newController)
{
[app.currentViewController removeFromParentViewController];
[app.rootViewController addChildViewController:newController];
app.currentViewController = newController;
}
See the following setup in my Storyboard:
I've got two container views in the first view controller, one for the main view that displays posts, and the other for a menu that the posts view slides out to reveal.
I'd like the menu bar to slide away like described here, which I have working but without the navigation bar. I've tried using Editor > Embed in Navigation Controller to both the view controller that contains the children (which worked, but when the menu slid out it still showed for the menu which doesn't look as intended) and when I just do it for the posts view controller it crashes with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setContainingViewController:]: unrecognized selector sent to instance 0x8e81c60'
Does anyone know how to accomplish this effect?
I assume this post is related to this one by Doug Smith, Why does hiding my status bar completely break my simple animation?. Your problem doesn't have anything to do with only one of the controllers being embedded in a navigation controller. It crashes, because you're trying to call setContainingViewController on a UINavigationController instead of the PostsViewController, which was (before you added the navigation controller) the destinationViewController of the embed segue. Since the navigation controller is now the destinationViewController, you need to change the code in prepareForSegue to this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Posts"]) {
UINavigationController *nav = (UINavigationController *)segue.destinationViewController;
PostsViewController *postsViewController = (PostsViewController *)nav.topViewController;
postsViewController.containingViewController = self;
}
if ([segue.identifier isEqualToString:#"Menu"]) {
MenuViewController *menuViewController = (MenuViewController *)segue.destinationViewController;
menuViewController.containingViewController = self;
}
}
I am having a silly problem with opening another viewcontroller on a buttonclick.
On my viewcontroller on my storyboard, I have a button. When a users clicks on this button, a new viewcontroller should be opened. As far as I know, there are 2 ways to do this.
in code
on the storyboard itself
Both methods are not working for me.
First the problem with storyboards:
(source: livefilestore.com)
I have connected my mainViewController with a navigationController, and I have connected my button with my next view (for adding an item). However, when I run the program, I get the following error:
'NSGenericException', reason: 'Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
I really don't understand this error, because the mainviewcontroller IS managed by a UINavigationController.
So, as this didn't work, I tried to handle it in code. I connected the buttonclick action to the mainViewController.h file and implemented it in the .m file.
This is the action method when a user clicks on the button:
- (IBAction)actionBtnAdd:(id)sender {
AddInventoryViewController *addinvController = [self.storyboard instantiateViewControllerWithIdentifier:#"AddInventoryViewController"];
[self presentViewController:addinvController animated:NO completion:nil];
}
The other viewcontroller is in the same storyboard, and the ID of the other viewcontroller is "AddInventoryViewController". This gives me the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Anybody who know the problem? What would be the best approach to solve this?
Your navigation controller is not the initial view controller. Select your nav controller on storyboard, go into Attributes Inspector and select "Is Initial View Controller".
Make your navigation controller Initial View Controller.
Then create a push-segue from button to target controller.
Then you can use following method to make some adjustments before pushing:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"TargetSegue"]) {
TargetViewController *vc = segue.destinationViewController;
//vc.items = nil;
}
}
Update:
There are more options:
If your initial view controller is tabbar: Add segue from one of the tabs to navigation controller which contains your view controller.
If your initial view controller is another navigation controller:
Remove navigation controller which contains your view controller. Then add the segue between the thirdViewController and the source from which it is presented. The you can use push segue between the button and the target view controller.
If you want to do it programmatically:
Remove navigation controller which contains your view controller. Push the thirdViewController from your currently presented navigation controller. Push the targetViewController from buttonAction.
Hope it will help :)
In your Xcode .Click the storyboard on the left side and on top select Editor->EmbedIn->Navigation controller .It will automatically add your navigation controller to the first view controller .Then it would work
You have to set the Navigation controller as the first controller
See before the Navigation controller there is the storyboard starting arrow
Under your IBAction method instead of using
[self presentViewController:addinvController animated:NO completion:nil];
use this code and check
[self.navigationController pushViewController:addinvController animated:YES];