I have a view controller that is modally presented, and I add it as an observer for a notification. After I dismiss the view controller, it keeps responding to the notification. Is this normal? If so, what should be done?
You should unregister the view controller from notification centre.
The good way to do that is do the register to notification in viewDidAppear method and unregister in viewDidDisappear.
Seems like you forget to remove observer after dismissing and your view controller is retained somewhere:
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Related
I have a custom in app notification appear at the top of any view controller on the screen when certain things happen.
Tapping on it triggers a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidTapOnNotification" object:nil];
The observer for that specific notification is the root navigation controller for my application, which I subclassed. I addObserver in viewDidLoad. This notification is always received, and the code I run in response is:
[CATransaction begin];
[CATransaction setCompletionBlock:^{ // this is called when the popToRootViewController animation completes
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NavigateToController" object:nil userInfo:notification.userInfo];
});
}];
[self popToRootViewControllerAnimated:YES];
[CATransaction commit];
I added a delay (dispatch_after) arbitrarily to see whether I just had to give my root view controller time to appear (i confirmed that it appeared before the 2 seconds are up).
Now, in my root view controller I again, add it as an observer for the NavigateToGroup notification. I.e. I call [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(openController:) name:#"NavigateToController" object:nil]; in viewDidLoad.
The problem is that the selector (i.e. the openController: method) is not always called. The only time it's called is when I'm already on that controller (the root controller) when I tap on the in app notification, then it works as expected. If I have other controllers on the navigation stack, tapping will popToRoot as expected, but then the method openController: will never get called even though I'm sure the notification gets posted (and i'm sure the view is appeared when it does).
Does anyone know what's going on here?
Or conversely, can anyone recommend a better way of handling this?
Turns out HariKrishnan.P's comment had some truth to it. NSNotifications don't seem to be posted if the view controller that is sending the message has already been popped. (Not sure if this is always the case but definitely seemed to be the case here)
I ended up refactoring the code to only send one initial notification then bypassing this by using delegation and calling my method explicitly through it rather then relying on on an NSNotification, which is probably a better solution anyways.
In my applicationWillEnterForeground, I check and send a notification if data refresh is necessary:
[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshModelNotification" object:nil];
The only observer for that particular notification is a particular view controller:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshData:) name:#"refreshModelNotification" object:nil];
That view controller is one of several inside a UITabBarController.
My question is: what happens if that view controller isn't the active tab when the notification is sent?
Thanks in advance.
If the observer is still set for the view controller the view controller will still receive the notification and behave normally except any visual changes to the view controller's view will not be seen
I have a Single View Application. When I hit the home button and ‘minimise’ the application I want to be able to execute code when the user reopens it.
For some reason viewDidAppear and viewWillAppear do not execute when I minimise and reopen the application.
Any suggestions?
Thanks in advance
sAdam
You can either execute code in the app delegate in
- (void)applicationDidBecomeActive:(UIApplication *)application
or register to observe the UIApplicationDidBecomeActiveNotification notification and execute your code in response.
There is also the notification UIApplicationWillEnterForegroundNotification and the method - (void)applicationWillEnterForeground:(UIApplication *)application in the app delegate.
To hook up notifications add this at an appropriate point
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
Define a the corresponding method
- (void)didBecomeActive:(NSNotification *)notification;
{
// Do some stuff
}
Then don't forget to remove yourself an observer at an appropriate point
[[NSNotificationCenter defaultCenter] removeObserver:self];
Discussion
You most likely only want your viewController to respond to events whilst it is the currently active view controller so a good place to register for the notifications would be viewDidLoad and then a good place to remove yourself as an observer would be viewDidUnload
If you are wanting to run the same logic that occurs in your viewDidAppear: method then abstract it into another method and have viewDidAppear: and the method that responds to the notification call this new method.
This is because since Apple implemented "Multitasking", apps are completely reloaded when you start them again, just as if you had never closed them. Because of this, there is no reason for viewDidAppear to be called.
You could either implement
- (void)applicationWillEnterForeground:(UIApplication *)application
and do there what ever you want. Or you register for the notification UIApplicationWillEnterForegroundNotification in your view controller. Do this in viewDidLoad:
[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myAppWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification object:nil];
And of course implement the specified selector and do there what you want.
I am not sure how the answer by #Paul.s performs the OP request since registering UIApplicationDidBecomeActiveNotification will be executed twice:
When launching the app
When application goes into the background
A better practice will be to decouple those events into 2 different notifications:
UIApplicationDidBecomeActiveNotification:
Posted when the app becomes active.
An app is active when it is receiving events. An active app can be said to have focus. It gains focus after being launched, loses focus when an overlay window pops up or when the device is locked, and gains focus when the device is unlocked.
Which basically means that all logic related to "when application launched for the first time"
UIApplicationWillEnterForegroundNotification:
Posted shortly before an app leaves the background state on its way to becoming the active app.
Conclusion
This way we can create a design that will perform both algorithms but as a decoupled way:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(yourMethodName1) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(yourMethodName2) name:UIApplicationDidBecomeActiveNotification object:nil];
This because you don't redraw your view. Use applicationWillEnterForeground in the AppDelegate instead. This should work fine for you.
I'm wondering if there is any way to return from UIViewController like 3-4 step backward, I've a main screen which will navigate to other UIViewController via presentModalViewController, on the next view, it will have a UINavigationBar which will navigate to a 4-5 level deeps. i wanna to put a button that let the user go back to the home directly without returning for all the view he enter.
thx in advance.
Have your root level view controller register as an observer of a notification, such as "POP_TO_ROOT". When it receives this notification, call a method to dismiss your modal view controller (or whatever is first on the stack).
In your viewcontroller stack, any of the views 4 or 5 levels in can just post a notification "POP_TO_ROOT".
EDIT: add code
In your main "screen" before you call presentModalViewController, do this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handlePopToRoot)
name:#"POP_TO_ROOT"
object:nil];
and add this method:
- (void) handlePopToRoot {
[[NotificationCenter defaultCenter] removeObserver:self
name:#"POP_TO_ROOT"
object:nil];
[self.navigationController dismissModalViewControllerAnimated: YES];
}
Then, down deep in your viewcontroller hierarchy, when you want to pop all the way out,
you just need to post a notification:
[[NSNotificationCenter defaultCenter] postNotification:#"POP_TO_ROOT" object:nil];
If I understand your question correctly, you are presenting a navigation controller (with a root view controller attached to it) from your "main view controller" modally, and you want to be able to get back to your "main view controller".
Because you will always have pointer to your navigation controller, you should be able to call
dismissModalViewControllerAnimated: from any of your view controllers and it will take you right back to the main view controller.
[[self.navigationController parentViewController] dismissModalViewControllerAnimated:YES]
Save your root View Controller in some property and call:
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
I ran into an issue similar to this earlier on the iPhone, but the same solution doesn't seem to apply to the iPad environment. My goal is to call my method forceReload, defined in my RootViewController, that will reload the countdown_table's data in the RootView from the DetailView once a modal is dismissed. forceReload works fine when called directly from the RootViewController, but I can't seem to point to point to the RootViewController from the DetailView.
I've tried using
RootViewController *root_view = [self.splitViewController.viewControllers objectAtIndex:0];
[root_view forceReload];
[root_view.countdown_table reloadData];
But that only points to the Navigation Controller, not the View Controller inside of it. Even when the modal is dismissed, RootViewController's viewWillAppear does not fire.
How can I point to this file?
Thanks!
Try using notifications, i.e., register RootViewController as an observer for notifications that your DetailView or any view may send.
in RootViewController's viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(forceReload)
name:#"reloadRequest"
object:nil];
viewDidUnload or in dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self];
And in your DetailViewController or your modal view (you didn't say they're the same), put this right before you dismiss the view or exactly when you need RootViewController to call forceReload:
NSNotification *notif = [NSNotification notificationWithName:#"reloadRequest" object:self];
[[NSNotificationCenter defaultCenter] postNotification:notif];