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.
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 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!
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;
}
We have redesigned our application to use a Tabbarcontroller as the main view. There is one instance where we need to display a record with enough data that it's also broken out into a tabbarcontroller. Previously we did our segue as showing below. I'm not sure what needs to change to segue between two tabbarcontrollers
SelectProvidersView *view = [[self storyboard] instantiateViewControllerWithIdentifier:#"careTeamView"];
view.careProviders = self.selectedPatient.careProviders;
[self.navigationController pushViewController:view animated:YES];
I'm guessing it's because I'm inside a tab controller instead of a navigation controller, I just don't know how this should be changed.
Don't use segue to switch between tabBarControllers. Use [(UITabBarController*)self.navigationController.topViewController setSelectedIndex:index]; to manually switch to the viewController you want
I am trying to accomplish the following:
What I would like to do is:
1.) When the user clicks on a button in UIViewController, it will retrieve a list of items.
2.) The items will then be displayed in UITableView.
3.) Lastly the number of items will be displayed on the third view (not initiated by UITableView)
All the above I manage, except that I would like to wrap all these controllers in a customized tab bar controller. But the data is not being passed from the UIViewController to the UITableView or the other View. I am using Storyboard, I hooked up tab bar controller and the other 3 controllers there as view controllers. I have tried instantiating UITableView and the UIView controllers using UIStoryboard method and creating instances of these controllers and send the data to them, the controllers are shown in tab bar controller, but data is not. I am not using Segue, by the way. Am I missing something here?
Thanks.
CODE:
UITabBarController tabBarController = (UITabBarController) self.window.rootViewController;
UIStoryboard *storyboard1 = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
mainViewController *mainController = (mainViewController*)[storyboard1 instantiateViewControllerWithIdentifier:#"mainController"];
UIStoryboard *storyboard2 = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
tableViewController *tableview = (tableViewController*)[storyboard2 instantiateViewControllerWithIdentifier:#"tableviewController"];
UIStoryboard *storyboard4 = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
resultController *mapView = (resultController*)[storyboard4 instantiateViewControllerWithIdentifier:#"resultView"];</code>
I have tried also creating the UI Table View and the result view controller in MainViewController and pass data to them (before they are even displayed), but no data is ever present when these controllers are displayed. I made sure I alloc and init these controllers properly and NSLog shows that the instance methods in these controllers are called, but still no data is shown, even though the controllers are displayed in tab bar view controller, when they are displayed.
UPDATE 2:
Now I am trying to do the following:
self.tableviewController = [self.tabBarController.viewControllers objectAtIndex:1];
self.resultViewController = [self.tabBarController.viewControllers objectAtIndex:2];
And when I call these:
NSLog(#"%#", [self.tabBarController class]);
NSLog(#"%#", [self.tableViewController class]);
NSLog(#"%#", [self.resultViewController class]);
They are all null.
Basically, I have 3 View Controllers hooked up as "view controllers" using storyboard to a UITabbarController. The main view controller (at index 0 in UITabbarController), from which I call the above, simply has one UIButton. Once pressed it will fetch a list of strings and send it to the Table View (at index 1 in UITabbarController) and on the last View Controller (at index 2 in UITabbarController) it will show just the number of strings as a result, which it receives from the Main View Controller, not from Table View.
I am not using any UINavigationController in my set up.
I would like to be able to fire off Table View and the Result View Controller before they are even displayed by clicking on the bar item tab on UITabbarController. By firing off I mean the data will be sent over to Table View and Result View even though they are not yet displayed.
The code you present is unnecessary and wrong for what you want to achieve. (Usually you should have one instance of UIStoryboard.) Read the docs.
You do not write anything about how you try to achieve the communication between the view controllers. There are several methods. But use delegation. Read the docs.
If you answer to a comment you should use the # character followed by the SO user name so he is notified about your updates.