Why do I want to use presentModalViewController in AppDelegate?
- Processing didReceiveLocalNotification, so I can launch a seperate modalView on top of my app to process the notification
What does my view architecture look like?
- Using storyboards
- MainStoryBoard: ->TabBarController->NavigationController
What's happening?
- Nothing, that's the problem :-D
- When I press the action button from the UILocalNotification, the app opens, but just shows the last open view from the tabbarcontroller.
As you can see below my last effort was to present the modalViewController on top of that current view, like so:
[self.window.rootViewController.tabBarController.selectedViewController.navigationController presentModalViewController:navigationController animated:YES];
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateInactive) {
// Application was in the background when notification was delivered.
NSLog(#"Received notification while in the background");
}
else {
NSLog(#"Received notification while running.");
}
MedicationReminderViewController *controller = [[UIStoryboard storyboardWithName:#"ModalStoryBoard" bundle:nil] instantiateViewControllerWithIdentifier:#"MedicationReminderVC"];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self.window.rootViewController.tabBarController.selectedViewController.navigationController presentModalViewController:navigationController animated:YES];
}
Update
Seems that this is nil:
self.window.rootViewController.tabBarController.selectedViewController.navigationController
Solution
[self.window.rootViewController presentModalViewController:navigationController animated:YES];
Try this :
[self.window.rootViewController presentModalViewController:controller
animated:YES];
Have you tried the following?
[self.window.rootViewController.tabBarController.selectedViewController presentModalViewController:navigationController animated:YES];
That said, even if this works, I would really urge you to reconsider your design choices to avoid having to do this. Traversing the navigation stack in this way to access stuff can get very messy and I'd strongly advise against it.
Related
In the default - (void)applicationDidBecomeActive:(UIApplication *)application { method in appDelegate, Apple includes the comment:
//This may also be a good place to direct someone
to a certain screen if the app was in background but they just clicked on a notification
I am trying to do this for local notifications, but can't figure out how to direct the user to a certain screen. The docs say that you can check for an URL but even so, I don't know how to specify an NSUrl for a screen within an app.
This seems like a common use case, we click on notifications all the time that take us to specific parts of app, but not finding any good resources.
Thanks in advance for any suggestions.
You have to use didReceiveLocalNotification or didReceiveNotificationResponse delegate method in your AppDelegate.m file based on which Notification you have implemented
For UILocalNotification:
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
// Your redirection code goes here like shown below
MyViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"ViewController"];
[self.window.rootViewController.navigationController pushViewController:controller animated:YES];
}
For UNNotification
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
// Your redirection code goes here like shown below
MyViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"ViewController"];
[self.window.rootViewController.navigationController pushViewController:controller animated:YES];
}
For more reference on UNNotification or handling Local Notification, please refer https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/SchedulingandHandlingLocalNotifications.html
I have tabbar controller having three tab, attached three navigation controller I want to go to second controller of navigation controller just like whatsapp. I handled successfully for background state, but for not running state. Below is my code in didfinishlaunch delegate method of uiapplication.
if (launchOptions != nil) {
// Launched from push notification
NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
[self performSelector:#selector(notificationObserverAfterDelay:) withObject:notification afterDelay:2.0];
}
-(void)notificationObserverAfterDelay:(NSDictionary *)userInfo
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshUIChatNotRunningState" object:self userInfo:userInfo];
}
Nazish Ali,
Sending out notification after delay of 2 seconds is really not a good idea.
Declare a method which accepts your userInfo dictionary as argument in your viewController having tab bar.
lets call it as,
YourViewController.h
-(void)appStartedFromPushNotification : (NSDictionary *)userInfo;
YourViewController.m
-(void)appStartedFromPushNotification : (NSDictionary *)userInfo {
//now the control has reached yourViewController with tab bar
//change the tab bar selection and perform segue and load the next viewController and use prepareforSgue to pass the data
}
Now why YourViewController only why not the actualViewController which anyway needs to be loaded ??? Reason you dont want mess with navigation stack manually. Because YourViewController is the child of UINavigationController and UINavigationController is your rootView controller YourViewController will be loaded automatically. So just access it and ask it to perform the tab change and load viewControllers so your navigation stack will continue to be stable :)
finally in Appdelegate,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *userInfo = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (launchOptions) {
[self handleReceiveRemoteNotification: userInfo];
}
return YES;
}
Declare a method in AppDelegate.m and access the YourViewController instance and handover data to it thats all :)
- (void) handleReceiveRemoteNotification:(NSDictionary *)userInfo{
UINavigationController *rootViewController = (UINavigationController *)self.window.rootViewController;
for(UIViewController *viewController in rootViewController.viewControllers){
if([viewController isKindOfClass:[YourViewController class]]) {
[viewController appStartedFromPushNotification:userInfo];
}
}
That should do the job :) Happy coding :)
I've got an app that is a list of itens in a table view and displays a detail view controller to every item on the table view. It also implements MMDrawerController (root view controller) as a side menu with storyboard.
I'm deep linking my app and using application open URL source application annotation method from App Delegate to handle it. So, I want to push a detail view controller from this method using MMDrawerController and I'm having some trouble.
Have a look in some code:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DetalheRestauranteViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"detalheRest"];
MMDrawerController* drawerController = (MMDrawerController *)self.window.rootViewController;
// If I use this nothing happens
[drawerController.centerViewController.navigationController pushViewController:vc animated:YES];
// If I use this nothing happens
[((MMDrawerController *)self.window.rootViewController).centerViewController.navigationController pushViewController:vc animated:YES];
// If I use this I got the unrecognized selector error
[(UINavigationController*)self.window.rootViewController pushViewController:vc animated:NO];
return YES;
}
Can someone help me with this?
I have found that with deep linking and the new iOS9 menu shortcuts that a small delay is often needed to display or manipulate UI Components.
I would try moving all of your UI/MMDrawer code to its own method. Then when openURL is called in your app delegate, call your new method on a delay.
So in ObjC it would look something like this:
[self performSelector:#selector(showDetailView) withObject:nil afterDelay:0.3];
do you solve this problem?
I tried many times. Finally I found it works fine for me today:
[((UINavigationController *)((MMDrawerController *)self.window.rootViewController).centerViewController) pushViewController:viewController animated:YES];
or
MMDrawerController *mvc = (MMDrawerController *)self.window.rootViewController;
UINavigationController *nvc = (UINavigationController *)mvc.centerViewController;
[nvc pushViewController:vc animated:YES];
These two kinds of writing are the same.
Here vc is one DetalheRestauranteViewController instance.
i have 2 local notifications in my app. i want to present view1 from notification1 and view2 from notification2 . how can i do it ? and i also want to show it on any view that was before going to inactive or background state by pressing home button.
UILocalNotification *notif1=[UILocalNotification alloc]init];
UILocalNotification *notif2=[UILocalNotification alloc]init];
//when user tapped on notif1
[self presentViewController:vc1 animated:YES completion:nil];
//when user tapped on notify2
[self presentViewController:vc2 animated:YES completion:nil];
You can use the -application:didReceiveLocalNotification:notification delegate method.
In your app delegate, implement this delegate as shown below and check if the app is currently active. If it isn't, tell the root view controller to present your new view controllers.
Here's an example:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
if (application.applicationState != UIApplicationStateActive) {
ViewControllerName *viewController = [ViewControllerName new];
[self.window.rootViewController presentViewController:viewController];
}
}
You will have to modify this a bit to handle the different notifications; however, that shouldn't be too difficult.
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.