Removing NSNotification in a UIViewController - ios

So I have a VC that i register for a notification in viewDidAppear: Even when the VC is not the main focus, as when I push another VC on the stack, I still want that VC to receive the notification. But after that VC is no longer needed, i.e. it is popped off the stack, I want to remove it as an observer for that notification.
Where do I do that? viewDid/WillUnload: are no more, and i tried dealloc, but it never gets called. So this seems to mean that the NotificaitonCenter will retain the VC, and it will never get released when it is popped off the stack.

In the dealloc method. That is the established convention nowadays.
The NotificationCenter is not retaining the VC. Where did you get that impression?

You should put the code in the dealloc method of the UIViewController
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
If you are using Swift then you can put it in the deinit method of the UIViewController
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

Related

navigationController popToRootViewControllerAnimated:YES does not call viewWillAppear

If I run, within IOS not swift,
[[navigationController popViewControllerAnimated:YES] viewWillAppear:YES]
it calls viewWillAppear on the previous VC.
I need to jump back to the root VC and have that ViewWillAppear called; however,
[[navigationController popToRootViewControllerAnimated:YES] viewWillAppear:YES] gives me a coding error.
Is there a way to do this without applicationWillEnterForeground: and applicationDidBecomeActive: methods in my appDelegate
I have tried within init
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground:)
name: UIApplicationWillEnterForegroundNotification
object:nil];
And in body
-(void) willEnterForeground:(Notification*)NotificationCenter
{
(self viewWillAppear:YES);
}
This doesn't seem to call viewWillAppear either.
Your code seems quite incorrect...
First, you should never call viewWillAppear - that is the system notifying your controller that the view will appear.
Second, just calling:
[self.navigationController popViewControllerAnimated:YES];
will navigate to the previous view controller in the stack, or:
[self.navigationController popToRootViewControllerAnimated:YES];
will navigate to the Root view controller.
In both cases, viewWillAppear is called by the system when the view, well, will appear.

Dealing with the same NSNotification in multiple view controllers

I'm using the below notifications to reload ViewControllerA when my app comes back from background mode. It works correctly, but the applicationEnteredForeground: method gets called every time when I open the app again. For example if I close the app when ViewControllerB or ViewControllerC is on the screen and open it again the method will be called despite the viewDidLoad of ViewControllerB doesn't contain applicationEnteredForeground: method. I would like to know that how could solve this issue? My goal is to use applicationEnteredForeground: only when ViewControllerA was on the screen before I closed the app.
As a possible solution I would just remove the NSNotificationCenter in the viewDidDisappear, but since the observer is in the viewDidLoad it won't work when the user navigates back, because viewDidLoad won't be called again. Is there any fix for this?
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)applicationEnteredForeground:(NSNotification *)notification {
// do stuff...
}
You should remove ViewController A's event listener on viewWillDisappear and add it in viewWillAppear. That way, VC A will only be listening when it is the visible view controller.
You can check if a view controller is on screen by checking the window property of it's view. It will work in most standard cases.
- (void)applicationEnteredForeground:(NSNotification *)notification
{
if (self.view.window == nil) {
// Not on screen
return;
}
// do stuff...
}

popToRootViewControllerAnimated not always firing viewWillAppear in rootViewController

My application handles opening email attachments. To do this in my AppDelegate I call my dedicated ViewController which is the RootViewController of my App thanks to popToRootViewControllerAnimated:.
I do some treatments in viewWillAppear and viewDidAppear of RootViewController but they are not called if the view controller that was displayed before switching to the mail application was my RootViewController!
Case 1 : Usual viewWillAppear / viewDidAppear methods aren't called :
(RootViewController -> switch to Mail App -> Open attachement in my app -> in AppDelegate popToRootViewControllerAnimated -> RootViewController)
Case 2 : Usual viewWillAppear / viewDidAppear methods are called :
(OtherViewController -> switch to Mail App -> Open attachement in my app -> in AppDelegate popToRootViewControllerAnimated -> RootViewController)
Found on the internet this trick but it doesn't work in case 1: http://www.idev101.com/code/User_Interface/UINavigationController/viewWillAppear.html
How can I fix this and call usual viewWillAppear / viewDidAppear methods all the time?
Your view controller is already visible so it's normal that viewWillAppear doesn't get called.
If you want to know when the user gets back to your app just add the following code in RootViewController init method :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground:)
name: UIApplicationWillEnterForegroundNotification
object:nil];
and implement what you want in
- (void)willEnterForeground:(NSNotification *)notification
Don't forget to stop observing on when RootViewController will be deallocated :
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Sounds like it is because in Case 1, within the context of your app your RootViewController is already 'visible' and therefor popToRootViewController doesn't have to do anything.
viewWillAppear: and viewDidAppear: methods are called only when such a thing happens relative to your application. These methods are not called when you switch between applications. You should depend on the applicationWillEnterForeground: and applicationDidBecomeActive: methods in your appDelegate.
One more thing to note is that when you are doing
RootViewController -> popToRootViewControllerAnimated -> RootViewController
Your root view controller's view is already visible and thus it will not fire the viewWillAppear: and viewDidAppear: methods.
I recommend you do something similar to this
RootViewController.m
-(void)viewDidAppear:(BOOL)animated
{
[self doSomething];
}
-(void)doSomething
{
//Your functionality
}
AppDelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.window.rootViewController doSomething];
}
You can also add your root view controller as a listener to the UIApplicationDidBecomeActiveNotification or UIApplicationWillEnterForegroundNotification

Why doesn't backgrounding and re-opening an app call viewDidAppear?

I have 2 view controllers, vc1 and vc2. A modal segue is invoked from vc1 when I want to load vc2. Say I background the app when vc2 is showing. Why isn't viewDidAppear called when the app is re-opened to the view that was left off? How else am I able to detect every time vc2 appears?
You could register for the UIApplicationDidBecomeActiveNotification in VC2 and call viewDidAppear from there. Do this in your viewDidLoad of VC2:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(somethingThatWillCallViewDidAppear:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
As rmaddy says below, make sure to remove the observer in dealloc or viewDidUnload.

removeObserver with NSNotification... what am I doing wrong?

Basically, I have a view1 which at some point, calls view2 (via presentModalViewController:animated:). When a certain UIButton in view2 is pressed, view2 is calls a notification method in view1 and immediately afterward is dismissed. The notification method pops up an alert.
The notification method works fine and is called appropriately. The problem is, every time view1 is created (only one view1 should exist at a time), I presumably get another NSNotification being created because if I go from view0 (the menu) to view1, then back and forth a few times, I get a series of the same alert message, one after another, from the notification method as many times as I opened a view1.
Here is my code, please tell me what I'm doing wrong:
View1.m
-(void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(showAlert:)
name:#"alert"
object:nil];
}
-(void) showAlert:(NSNotification*)notification {
// (I've also tried to swap the removeObserver method from dealloc
// to here, but it still fails to remove the observer.)
// < UIAlertView code to pop up a message here. >
}
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
View2.m
-(IBAction) buttonWasTapped {
[[NSNotificationCenter defaultCenter] postNotificationName:#"alert"
object:nil];
[self dismissModalViewControllerAnimated:YES];
}
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Calling -dealloc doesn't automatically happen after the view controller is dismissed — there can still be some "life" left in the view controller's lifetime. In that timeframe, that view controller is still subscribed for that notification.
If you remove the observer in -viewWillDisappear: or -viewDidDisappear:, this will have a more immediate effect:
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"alert"
object:nil];
}
If you implement the removal of Observer in the viewWillDisappear: or viewDidDisappear: then you should not leave the addition of the observer in the viewDidLoad.
Instead put the addition of the observer in the viewWillAppear:. The problem you are having is because when any view is shown onto of the UIViewController view the removal of your observer will occur and since you added observer in viewDidLoad which will happen only once, it will be lost.
Keep in mind that this approach works well for objects you do not wish to observer while your main view is not in the fore front.
Also Keep in mind that viewDidUnload has been depreciated too.
There is nothing wrong putting removeObserver: in dealloc. Just the fact it's not called means view1 not properly releases after dismissing. Looks like something holds pointer to your view1, check for retain cycles.
Also, you shouldn't call dealloc on super.

Resources