I spent some time on trial-and-error last week to fix a bug in my app. I thought the details of what I'd done wrong and what the fix was might be useful for others.
As part of this change, I've added a
UITabBarController, with each tab holding a navigation controller.
The problem was that launching the app via a local notification caused it to crash. The notification is supposed to create and push a view controller,
ReportViewController, that's normally only accessed by tapping on cells within the main view controller,
MoodListViewController (which is now one of three view controllers inside the tab bar controller). My old code in the delegate's
didReceiveLocalNotification method looked like this:
// Create report view ReportViewController *reportVC = (ReportViewController *) [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:@"reportVC"]; // Show report view [self.window.rootViewController showViewController:reportVC sender:self];
In my old version,
self.window.rootViewController was the
MoodListViewController, so this code worked reliably. The problem in the new version is that
self.window.rootViewController is now a tab bar controller. This caused two problems:
The tab bar controller couldn't show my
ReportViewController. I needed to get to my
MoodListViewController so its navigation controller could push the report view.
Secondly, the tab bar wasn't created in a storyboard, but to create the
ReportViewController, which was created in a storyboard, I'm looking for
self.window.rootViewController.storyboard. Since the
MoodListController is no longer the root view controller, this method doesn't return me a storyboard, and thus my report view was never being initialised.
What I ended up doing to create the report view was this:
// Create report view UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; ReportViewController *reportVC = [storyboard instantiateViewControllerWithIdentifier:@"reportVC"];
Retrieving my storyboard with
storyboardWithName is much more reliable, since I know the
ReportViewController exists in this storyboard, and I'm no longer making assumptions about what the root view controller is (or whether its storyboard is the one I need).
To show the report view once it's instantiated with the data it needs, I'm doing this:
// Show report vc UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController; [tabBarController setSelectedIndex:1]; // This is the mood list // Each VC's nav controller is added to the tabbar //not the VC itself so I need to access the nav controller // via the tabbar to open a new report vc UINavigationController *moodNav = (UINavigationController *) tabBarController.selectedViewController; [moodNav pushViewController:reportVC animated:NO];
This is probably not the best way to do it, since I'm choosing an item from the tab bar controller using an index and then assuming (which is right now, but won't necessarily always be) that it's the mood list's nav controller. If I ever change the order of the tabs, this will be a problem.
From the research I've done, it seems like using the index to choose which view controller to select is the most common approach. And since the tab bar's view controllers are accessible as an array, I can't use a key to get the right one. So for now I don't know a better way to approach this, but I've fixed my bug. Phew!