Changing UIViewController from AppDelegate on ios for push notification - ios

When a user gets a push notification, I want the app to open up to a certain UIViewController. I'm in xCode 5 with storyboard.
I think my code is close but it is throwing an error
Here is the method in AppDelegate
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
UINavigationController *nav = (UINavigationController *)self.window.rootViewController;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
RewardList *vc = (RewardList *)[storyboard instantiateViewControllerWithIdentifier:#"RewardList"];
[nav pushViewController:vc animated:YES];
}
It errors out on the [nav pushViewController:vc animated:YES]; I'm not quite sure what is going on. Can someone help shed light on this issue?
Here is the error:
-[SplashViewController pushViewController:animated:]: unrecognized selector sent to instance 0x1f862630 2013-12-12 17:58:28.719 appName[473:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SplashViewController pushViewController:animated:]: unrecognized selector sent to instance 0x1f862630'
EDIT:
The app root view controller is SplashNavigationController which is a UIViewController. From there I have a UITabBarController. Inside of that on my 3rd tab, is RewardList which is also a UIViewController. I need to go from whatever the current view is, to my 3rd tab, the RewardList tab.
I'm finding a lot of posts on this, but nothing seems to work in my case.

The first thing I would do to fix this is change the structure so the tab bar controller is the root view controller of the window. If you want your splash screen to come up first, then present that modally (with no animation) from the viewDidAppear method in the controller in the first tab. When you're done with that screen, you can just dismiss it, and you'll be in the first controller. If you make that change, then doing what you want in the app delegate will be simple,
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UITabBarController *tbc = (UITabBarController *)self.window.rootViewController;
tbc.selectedIndex = 2;
}
I would also suggest that you study Apple's "View Controller Programming Guide for iOS" to learn about view controllers and their life cycle.

It sounds like your root view controller is a view controller of the class SplashViewController. Is SplashViewController a subclass of UINavigationController? It has to be, or you'll get that error.

There are some ways to get top most view controller on this thread.
https://stackoverflow.com/a/23603265/
I like this code because it's very simple.
https://stackoverflow.com/a/23603265/1971596
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
And then you just present view controller from top most view controller.

Related

iOS - navigate From AppDelegate to UITableViewController

I try to navigate to certain ViewController after I get remote Notification
It crashes after this code any help please
My storyboard like this
SWRevealViewController -> NavViewController -> UIViewController
I want to reach this UIViewController
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
UIStoryboard *storyboard =[UIStoryboard storyboardWithName:#"Main" bundle:nil];
// my ViewController I want navigate to UITableViewController call home_tableview
Home_tableView *home =[storyboard instantiateViewControllerWithIdentifier:#"home_view"];
[(UINavigationController *)self.window.rootViewController pushViewController:home animated:NO];
}
The Error I get
2015-01-20 13:04:14.379 SchoolLink[1304:416727] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:(0x25bd749f 0x3338dc8b 0x25af30b3 0x2909d06b 0x11d48f 0x29099d0f 0x29099a7d 0x2909f953 0x2910643d 0x111dbd 0x2930286b 0x292fa54d 0x2c3680d1 0x25b9dd7d 0x25b9d041 0x25b9bb7b 0x25ae93c1 0x25ae91d3 0x2cee70a9 0x290f8fa1 0x12b7ad 0x3390daaf)
libc++abi.dylib: terminating with
I suppose you have an UINavigationController in your storyboard which embeds your view controllers.
If that's the case, your code should work without any issues.
I've tested it in a new project with two view controllers and one navigation controller as the initial view and everything is fine.
If you're certain that you have all the storyboard identifiers right, and that your initial View in Interface Builder is an UINavigationController that embeds all of your ViewControllers, then you have some issues elsewhere, not in the code you've pasted in.
Since your error is related to insertObjectAtIndex: I believe there's some data you instantiate your UITableViewController with in the normal workflow of the app (when segueing to it) and when you try to present the UITableView controller in a clean state from a notification, that data is missing, hence the crash.
Check where you try to add an object to an array at a given index.
The problem is either in the class that receives the notification (I guess it's your AppDelegate class), either in the UITableViewController class, somewhere in the initialization.
Try
Home_tableView *home =[storyboard instantiateViewControllerWithIdentifier:#"home_view"];
[self.window setRootViewController:home]
[self.window makeKeyAndVisible];
Please try this:
UIStoryboard *storyboard =[UIStoryboard storyboardWithName:#"Main" bundle:nil];
Home_tableView*svc = [storyboard instantiateViewControllerWithIdentifier:#"home_view"];
// Configure the new view controller here.
[self presentViewController:svc animated:YES completion:nil];

Pushing ViewController from app delegate

I am using a storyboard in Xcode 5, which appears as so:
My requirement is to push a ViewController (VIEW1 or VIEW2) into view from the app delegate. Essentially it should not matter what view is presently on the screen -- I would just like to make a ViewController appear when the app delegate picks up an external event.
In order to try and achieve this, I have property references to both the TabBarCtrl-Products and NavCtrl-ProductA in my app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_tabBarProducts = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidTabBarProducts"];
NSArray *tabvcs = _tabBarProducts.viewControllers;
for (id controller in tabvcs){
if ([controller isKindOfClass:[VCNavControl_ProductA class]]) {
_navControllerProductA = controller;
break;
}
}
return YES;
}
The app delegate method to push VIEW2 is:
-(void)showVCVIEW2
{
VC_V2 *targetvc = nil;
targetvc = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidView2"];
[[AppDelegate sharedInstance].navControllerProductA pushViewController:targetvc animated:NO];
}
This works OK when VIEW1 is showing at the time showVCVIEW2 is called, however it does not work when ViewCtrl-ProductB is showing. I can see that the new instance of targetvc has been added to the AppDelegate _navControllerProductA's stack, however it does not display.
(Regarding the setting of the the app delegate's rootViewController, I set this to _tabBarProducts after the VC-Splash and VC-Setup ViewCtrls have finished).
I would appreciate very much if anyone can give me an idea on how to achieve this. I suspect my problems stem from having a NavCtrl in a TabBarCtrl, but I do not know a way around this.
Your problem is that the navigation controller you are pushing on is not in the view hierarchy.
You instead could try setting the tabBarController's selected index like this:
[self.tabBarController setSelectedIndex:1];

App desing: UITabBarController inside UINavigationController

My app has to have a launch screen where I perform some operations such as updating web content. After the process is done, I display the whole app interface within a UITabBarController. At some point, the app has to go back to this launch view controller to handle the update of the application data.
Apple specifically states that a UITabBarController should be the root view controller of any app.
I'm looking for clever ways of presenting a UIViewController before a UITabBarController without embedding both of them in a UINavigationController.
I currently have the setup I want to avoid (UINavigationController -> UITabBarController) because it works and makes sense. I'm afraid Apple wont like it, so i'm looking forward for some light in the subject.
However, nothing that I've read says that the root controller has to remain the same throughout the life of the app. What about something like...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.tabController = (UITabBarController *)[self.window rootViewController];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.altController = [storyboard instantiateViewControllerWithIdentifier:#"AlternateController"];
return YES;
}
- (void)swapRootControllers {
if ([[self.window rootViewController] isKindOfClass:[UITabBarController class]]) {
self.window.rootViewController = self.altController;
} else {
self.window.rootViewController = self.tabController;
}
}
...assuming all the supporting variable declarations and storyboard implementation.

Navigating from Appdelegate when push message received

Currently this is how my method loooks
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSString *screenNo =[userInfo objectForKey:#"screen"];
}
Based on the screenNo I would like to navigate to different view controllers. But I couldn't do as most of the answers given below.
Reason is that my root view is not navigation control, so I couldn't segue. It crashes the app.
when push message arrives didReceiveRemoteNotification is called and I could see the content of the message too. But it doesn't get navigated using the methods shown here.
[self.window makeKeyAndVisible];
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"galleryViewController2"];
[(UINavigationController *)self.window.rootViewController pushViewController:vc animated:YES];
this is the exception
2014-07-21 18:06:53.709 Proitzen Rest[993:60b] -[RESTSecondViewController pushViewController:animated:]: unrecognized selector sent to instance 0x14e26270
2014-07-21 18:06:53.712 Proitzen Rest[993:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RESTSecondViewController pushViewController:animated:]: unrecognized selector sent to instance 0x14e26270'
*** First throw call stack:
(0x2f480fd3 0x3a021ccf 0x2f484967 0x2f483253 0x2f3d27b8 0xff93b 0x31eb3b29 0x31eb37fb 0x31dbb05f 0x31e6d377 0x31d1c6f5 0x31c9555b 0x2f44c2a5 0x2f449c49 0x2f449f8b 0x2f3b4f0f 0x2f3b4cf3 0x342da663 0x31d0016d 0x157e69 0x3a52eab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
Thanks for your time in advance.
Did you try something like this?
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"galleryViewController2"];
self.window.rootViewController = vc;
Instead of pushing your new controller (it crashes because to push you need a navigation controller) you can replace current controller with the new one.
Please, take in account that you can not pop to the original controller (if you need to get back, you need a navigation)
You're trying to push a UIViewController with a UIViewController. This is not possible. You must have a UINavigationController in your app hierarchy.
You can also just set the rootViewController:
[self.window setRootViewController: newViewController];
make sure you call this method before trying to present any view controller.
[self.window makeKeyAndVisible];
You can't navigate using push and Pop from APPDelegate if you need to navigate from appDelegate to a file, Segues won't help either then You would need to load it first in your window and then make it Visible such as..
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSString *screenNo =[userInfo objectForKey:#"screen"];
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
firstViewController *menu = [navController.storyboard instantiateViewControllerWithIdentifier:#"firstVC"];
// First item in array is bottom of stack, last item is top.
navController.viewControllers = [NSArray arrayWithObjects:menu, nil];
[self.window makeKeyAndVisible];
}
This is what finally saved me. placed it inside didReceiveRemoteNotification method.
NSLog(#"User wanna navigate");
[self.window makeKeyAndVisible];
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"moreTableViewController"];
self.window.rootViewController = vc;
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
UIViewController *evc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"eventsViewController"];
[navController.visibleViewController.navigationController pushViewController:evc animated:YES];

open rootViewController with button

I recently work on a tutorial for my app. I created the tutorial with this Tutorial:
http://www.appcoda.com/uipageviewcontroller-tutorial-intro/
I created a "button" which brings the user back to the rootViewController, in this case the TabBarController. Problem is: with this tutorial I made a extra storyboard for the tutorial. So how can I go back to the original rootViewController(TabBarController) with the button?
Code:
- (IBAction)start:(id)sender {
UIViewController* backToRootViewController = [[UIViewController alloc] initWithNibName:#"TabBarController" bundle:[NSBundle mainBundle]];
[self.view addSubview:backToRootViewController.view];
}
This does not work, too
- (IBAction)start:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
EDIT
To open the tutorial at the first start:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[APPViewController alloc] initWithNibName:#"APPViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
}
APPViewController is the Tutorial
EDIT
After the help of johnnelm9r the current code is this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"main" bundle: nil];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
[self.window.rootViewController presentViewController:introViewController animated:NO completion:nil];
}
But now, sadly, the app crashed and the error is:
Application tried to present a nil modal view controller on target <UITabBarController: 0x175409d0>.'
and also a warning: Incompatible pointer type assigning to 'ViewController' from 'UIViewController' in AppDelegate
*****UPDATE****
I uploaded a sample project to demonstrate what I mean. You can find it here: github link
Good luck!
**** EDIT
After talking with you more I'd suggest trying to use something similar to this code in your viewDidAppear method of whatever view controller runs the first tab of your tabbarcontroller (obviously you want your own class name where I have IntroViewController):
BOOL didShowIntro = [[NSUserDefaults standardUserDefaults] boolForKey:#"showIntro"];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
if (didShowIntro)
{
[self presentViewController:introViewController animated:NO completion:nil];
}
and then just pop the presented controller from your button in the tutorial like so:
[self dismissViewControllerAnimated:NO completion:nil];
Just remember to set your user defaults to no when you press the button in your tutorial view controller. And make sure you are going from tabbarcontroller to navigationcontroller as a relationship segue and then to the view controller you want to show after the tutorial as a root. Just to be clear: there should be no connection to the tutorial view controller on your storyboard.
Just to be extra clear: your storyboard should have the navbarcontroller as your initial view controller connected to a navigationcontroller connected to a view controller and another view controller not connected to anything. :) And make sure you delete the code in the delegate.
To go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];

Resources