Do hidden views respond to NSNotifications? - iphone

I have a two tabs on a UITabBarController and I have registered both with NSNotificationCenter, my problem is I am not getting notifications on the hidden tab (i.e. its not called viewDidAppear: yet). My thinking is that controllers not on screen (i.e. hidden) do not respond to NSNotifications. I can do things a different way thats not a problem, but I just want to verify why the hidden tab is not getting the notification in case I am missing something else and it should actually be working ...
EDIT:
#Fab1n pointed me in the right direction, I had mistakenly used viewWillDisappear: to remove the observer, so when the view disappeared is was no longer listening out for notifications. I will move it to dealloc.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
Changed to:
- (void)dealloc {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
Much appreciated.

To stay safe: Register your NSNotification in -init or (with IB) -initWithNibName:
A controller of a UITabbarController is initialized with init when you set the controllers property of the UITabbarController.
viewWillAppear and viewDidAppear are called each time you make one of the controllers visible by tapping on a tabbar icon.
Solution:
register your NSNotification by overriding -init in each of your 'sub'-controllers
unregister the notification in -dealloc (don't call [super dealloc] with ARC!!!)
Now everything works fine!

As far as i know any tab bar element is lazy loaded despite the first one. They get loaded the first time you tap on the representing tab bar item (or by accessing their view property programmatically). Afterwards they can receive any message you want even if you select another tab item.

Related

NSNotificationCenter with respect to ViewWillAppear and ViewWillDisapper

I have a simple viewController that I want to listen for UIKeyboardWillHideNotification. Therefore I have the following code:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden)
name:UIKeyboardWillHideNotification object:nil];
}
- (void) keyboardWillBeHidden
{
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
I'm trying to decide when to remove the viewController as an notification center observer. I only need to know about the UIKeyboardWillHideNotification when the viewcontroller is on screen, thus I'm thinking about adding the following:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Is this sufficient? Is there ever a chance that viewDidUnload or dealloc will get called while the viewController is still on screen? Note that I'm using a very basic UINavigationController for the flow of my app.
Registering the notification in viewWillAppear and unregistering it in viewWillDisappear seems to be a clean and symmetric solution to me.
Note that viewWillAppear can be called multiple times before dealloc (e.g. if another view controller is pushed onto your VC, or if you switch between tab bar controllers.) If you register the notification in viewWillAppear and unregister it only in dealloc then you will get duplicate registrations (compare Warning for iOS/iPhone users about duplicate NSNotification observations) and the registered selector is called multiple times for a single notification event.
I actually prefer the block-based observer registration method
addObserverForName:object:queue:usingBlock:
which returns an opaque object which is used for removing the observer again. Storing this return value into an instance variable of your view controller helps to keep track if the observer is already registered or not, and therefore helps to avoid duplicate registrations.
To answer your direct question, dealloc will never be called while your view is still on screen unless you directly call it which you shouldn't be.
dealloc will only be called when there are no strong pointers remaining that point to your viewController.
As Anoop Vaidya suggests, it is totally doable to put removeObserver in dealloc and be confident that dealloc won't get called while your viewController is on screen, and if it does... well you have much bigger problems than removing an observer
Edit: Since I can't actually reply to comments yet, when your viewController is off screen it is actually deallocated. It is then re-instantiated when it is called back on screen.
Edit: I'm wrong

What's the better way to addObserver/removeObserver with NSNotificationCenter?

I used to addObserver in viewDidLoad: and removeObserver in dealloc:. Code:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshData)
name:AnyNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AnyNotification
object:nil];
}
But according to some articles said, it's better to addObserver in viewDidAppear: and removeObserver in viewDidDisappear:. Code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshData)
name:AnyNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AnyNotification
object:nil];
}
So, what's the better way to addObserver/removeObserver?
this depends on your scenario, usually the best approach is to add in viewDidLoad and remove in dealloc and in viewDidUnload (deprecated in iOS 9.0, use dealloc only), but there are some cases when you have same method in different classes like UI effects and want to call only current screen's method using notification, then you will have to add the observer in viewWillAppear and remove it in viewWillDisappear or viewDidAppear/viewDidDisappear
Edit:
A note from comments, thanks #honey.
Though now since iOS 9, you no longer need to care about removing the observer. See Apple release notes: "In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated..
I would normally put it in -viewDidAppear: and -viewDidDisapear: (or -viewWillAppear: and -viewWillDisappear:) simply because in every case I came across I'm only interested in the notification if the view is actually displayed.
It's probably a premature optimisation (your code for handling the notification could take some time, but might be useless if the view is not displayed), but then it's also no more code - it's the same code just in a different method...
Don't forget NSKeyValueObservingOptionInitial. I use it with viewWillAppear/viewWillDisappear so my UI is always up-do-date, even if I hide that view controller, saving resources because I will not update it until is shown again.
The best approach using NSNotifications is adding the observer when you need to observe for notifications and remove them when you don't need them anymore.
This could be on viewDidLoad:, viewWillAppear:, or when the user taps some button etc.
I will give you a little example:
My app has a tabbar, and in some of the view controllers, I'm displaying some info downloaded from internet (a tweet for example). I also have a class pooling for new data from server each 2 minutes, and as the server had new data, I updated the info on database. I will not use a delegate pattern to listen to DB changes, because I have so many view controllers displaying data, and it will be a very bad design making the delegate an array and looping to pass the data for every view controller. So, in this specific scenario, the best to do is to post a notification telling every VC that new data has come.
If your VC removes the delegate when the view disappears, only the current one will receive the notification and update the displaying contents.
You obviously could update the contents of the other VCs before display, on viewWillAppear: for example, but doing this the VC contents will be updated not only when necessary, but each time you change tabs.
It was only one example, where I tried to show you that for NSNotifications, is difficult to advise you when to add or remove observers when we don't have the entire description of how you app behaviours.
-viewWillAppear: + -viewWillDisappear: is better than -viewDidAppear: + -viewDidDisapear:, because they are always called the same number of times.

nsnotificationcenter method fired more than once

i have a viewcontroller .In it there is a nsnotification observer in it. i am posting the notification from another viewcontroller.but the nsnotification observers selector get fired two or sometimes three times. My question is that when i use [view removeFromSuperview];
to remove this viewcontrollers view ,is the notification observer removed? I have given this method at the dealloc method of the viewcontroller class
- (void)dealloc {
[super dealloc];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
No.
that method will be called when the viewcontrollers retain count becomes 0
You should add another method that will be called when the view is removed from the other viewcontroller and call
[[NSNotificationCenter defaultCenter] removeObserver:self];
For the issue that the selector is called multiple times, I would need to see more code - make sure that the line of code thats posting the notification isnt being called multiple times
NSNotification registered to whole app (or even for all operating system), not to single view or viewcontroller. You have need for remove observer in your action if it won't longer used. In this case you can handle only one posted notification.

NSNotification not being received in a UIViewController whose view is a subview of a UIScrollview

I'm trying to subscribe myViewController to a custom NSNotification. The view heirarchy looks like this:
window
- rootViewController.view
- scrollView
- myViewController.view
The notification is being sent to the [NSNotificationCenter defaultCenter] from a button inside the rootViewController's view. The rootViewController listens for the notification and responds to it just fine.
I have the same exact "listening" code inside myViewController, but it's not receiving the notification for some reason. If it's part of the app it should receive the notification, correct? I have a debug message inside myViewController's initWithNibName method, so I know that it's subscribing to the notification. I even tried having myViewController listen for ALL notifications by setting the notification name to nil. For example:
NSLog(#"main view controller initialized");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarVisibilityChanged:) name:nil object:nil];
But no luck there either. Has anyone seen anything like this happen before with NSNotifications?
Any ideas?
Thanks!
I guess the view controller is deallocated. It's a common mistake not to retain a view controller when creating it programmatically.
If you are sure that the observation is set up correctly, my only explanation for not receiving any notifications is, that the controller is deallocated before any notifications are being sent.
The NSNotification mechanism is not dependent on neither view hierarchy nor thread. If you have a (living) object that has been set up as an observer it will definitely receive notifications.

App Delegate - Unload View Controller

I'm trying to unload a view controller from view when the iPhone goes to sleep. My app has a stopwatch that has to keep counting when the phone goes to sleep or call comes in or the user closes the app without logging out.
I have all this functionality in place, I'm capturing all start times and stop times and upon re-entering the stopwatch view controller, I calculate the difference. It all works beautifully. When I was doing some additional testing I realised I hadn't catered for the iPhone going into sleep mode.
So all I need to do to make sure my stopwatch is correct bring the user back to the app home screen. I know the following method is called when the app goes to sleep:
-(void)applicationWillResignActive:(UIApplication *)application
How do I unload the stopwatch view controller from my app delegate ?
---- UPDATE ----
kpower, thanks for your feedback. I've implemented the following code:
In my App Delegate:
- (void)applicationWillResignActive:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AppIsAsleep" object:nil];
}
In my view controller, I have the following:
-(void)viewDidLoad
{
// Add Observer.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidUnload:) name:#"AppIsAsleep" object:nil];
}
- (void)viewDidUnload {
//Remove the Observer.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"AppIsAsleep" object:nil];
}
When the phone goes to sleep, it actually closes the app, am I doing something wrong ?
Regards,
Stephen
You can use the Notifications mechanism. It allows you to unload view controller from different place (not the AppDelegate) this case.
For example, in your view controller's viewDidLoad method you add an observer (don't forget to remove it in viewDidUnload) and in applicationWillResignActive: method of AppDelegate you just simply post notification. That's all.
↓ Update here ↓
When you get a notification - you should manage view controller's removing by yourself. And calling viewDidUnload here is not the solution, cause this method is called after view controller was already unloaded and doesn't cause removing.
How to remove? Depends on how the view controller was added (for example, popViewControllerAnimated for UINavigationController). The main idea here is to make object's retain count equal to 0 (as you know this case an object will be destroyed) - so you should sent release message necessary amount of times.