Swift - notification observer is called several times - ios

I have viewController and inside in viewDidLoad I have
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showNextQuestions", name: "showNextQuestionsID", object: nil)
In another controller I have
NSNotificationCenter.defaultCenter().postNotificationName("showNextQuestionsID", object: nil)
If I go home from app and launch it again function showNextQuestionID fires two times.
I tried to use
func applicationDidEnterBackground(application: UIApplication) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
}
But this doesn't help,
and in viewController
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
How can I fix this ?

You are not removing your notification observer in the right place. You register the observer in your view controller subclass, and you need to remove it in the same class. A logical place is to override the viewWillDisappear method. Place the following code in your view controller subclass:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Also remove
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
From your AppDelegate. When you supply the 'self' argument in the AppDelegate, it is referring to the AppDelegate class, not your view controller. When you call to remove your notification observer in the view controller sublcass, self is your view controller, which is what you want.
Last, when you call simply removeObserver(self) with no other arguments, it will unregister all the observers for that object. That way you don't have to go through and list each observer by name.

Put your observers in AppDelegate or a singleton, so that you can easily add and remove an observer during application states.

applicationDidEnterBackground and deinit should be ok.
The problem is the way you are trying to remove the observer in applicationDidEnterBackground. You are trying to remove the observer from AppDelegate and you need to remove the observer from your ViewController.
To fix the problem:
1) Listen for UIApplicationDidEnterBackgroundNotification in your view controller:
func init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myAppDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
}
2) Implement the method that listen to UIApplicationDidEnterBackgroundNotification
func myAppDidEnterBackground() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
}
3) Extra. You could also listen for UIApplicationWillEnterForegroundNotification in order to add again your custom notification

Related

UITabBar Lifecycle's methods are not fired from background start

I've have a UITabBar controller as main controller, with 2 tabs. Each tab is a NavigatorViewController with a UIViewController embedded.
If I open the application from background after a previous cold launch, none of the ViewWillAppear (UITabBarController, UIViewController) is fired.
How can I call the lifecycle of UITabBarChildren when user come from backgroud? (IE: From a notification)
That is not in the life cycle because the state of controllers is not changing during background mode or other application events.
You should observe for applicationWillEnterForegroundNotification
class VC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Listen for application event somewhere early like `ViewDidLoad`
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
}
// Implement a function that you want to execute when event happen
#objc func applicationWillEnterForegroundNotification() {
// Do anything before application Enter Foreground
}
// Remove observer when the controller is going to remove to prevent further issues
deinit {
NotificationCenter.default.removeObserver(self)
}
}
When application comes from background non viewWillAppear/viewDidAppear is called for any active vc , you need to listen to app delegate like applicationWillEnterForegroundNotification
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
#objc func applicationWillEnterForegroundNotification(_ notification: NSNotification) {
print("To-Do")
}
You can add an observer to UIApplicationWillEnterForeground in your controllers.
Posted shortly before an app leaves the background state on its way to
becoming the active app.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,selector: #selector(self.appEnteredFromBackground(_:)),name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func appEnteredFromBackground(_ notification: NSNotification) {
print("From background")
}

Where to implement applicationWillResignActive(_:)?

In order for the ViewController to "do something specific" just before the app goes into the background state...
I am to understand that this sort of thing is generally handled inside the
applicationWillResignActive(_:) method, but this method resides inside the AppDelegate class, not the ViewController.
This is my first time doing lifecycle related stuff on IOS, and so I'm not sure whether to:
1) Call a ViewController method from inside the AppDelegate class. This would mean that I have to change the method from private to public.
2) Have the ViewController implement UIApplicationDelegate
PS - Is it okay to just delete the AppDelegate class as long as the ViewController implements UIApplication delegate instead?
EDIT: I should add that this is a single-page app with only one view controller (well, I suppose it will have a settings view controller eventually... but the 'ViewController' that I am referring to will never be popped off the stack).
Thanks!
Generally you shouldn't delete the AppDelegate unless you have a really good reason. This isn't a good reason.
For your scenario I would investigate using NotificationCenter to observe the UIApplicationWillResignActive event. This event is fired every time the application will enter the background.
For more information see: Apple Docs
e.g.
func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated: animated)
NotificationCenter.default.addObserver(self, selector: #selector(youFunction), name: .UIApplicationWillResignActive, object: nil)
}
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated: animated)
NotificationCenter.default.removeObserver(self)
}
Use NotificationCenter
In YourViewController
class YourViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(reloadTableData(_:)), name: .UIApplicationWillResignActive, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive, object: nil)
}
}
func reloadTableData(_ notification: Notification) {
}

IOS Swift how reload viewcontroller when app come from background?

I want when user call app from background to reaload one function from viewcontroller?
Elaborating on nishith's answer:
Add the following code to your view controller you want to refresh
override func viewWillAppear() {
super.viewWillAppear()
......
......
NotificationCenter.default.addObserver(self, selector:#selector(YourViewController.methodToRefresh), name: UIApplication.willEnterForegroundNotification, object: UIApplication.shared)
......
......
}
Always remember to cleanup when the view disappears in:
override func viewWillDisappear(animated: Bool) {
NotificationCenter.default.removeObserver(self)
}
You can register your controller for these notifications and reload your controller accordingly.
UIApplicationDidEnterBackgroundNotification
UIApplicationWillEnterForegroundNotification

How to respond to notifications with methods of visible view's controller only?

On the first UITableViewController scene of my project I use UITextFields in cells to make some of their content possible to rename on the fly. I had to implement notification center observers to manage tableView insets when keyboard appears and hides the cell, that is being modified.
override func viewDidLoad() {
(...)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name:UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name:UIKeyboardWillHideNotification, object: nil)
(...)
}
I aslo put in deinit code to remove these observers:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name:UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name:UIKeyboardWillHideNotification, object: nil)
}
However when I perform a segue to another scene, and keyboard appears I recieve runtime exception 'fatal error: unexpectedly found nil while unwrapping an Optional value' with a line of the previous UItableViewController code, that should not be active on this scene:
func keyboardWasShown(notification: NSNotification) {
(...)
if let cell = tableView.cellForRowAtIndexPath(editingIndexPath!) as? ListsTableViewCell { // ERROR HERE
(...)
}
}
I suppose this is because notifications run globally. But I didn't expect, that currently invisible view's controller will respond to notification with own functon.
The question is: how to force controllers react to notifications only when their view is visible. In my case deinit {} does not work.
The lifecycle of a view controller is as follows:
init -> loadView -> viewDidLoad -> viewWillAppear: -> viewDidAppear: -> viewWillDisappear: -> viewDidDisappear: -> deinit
When you push or present another view controller (controller B), the current view controller (controller A) calls its viewWillDisappear:and viewDidDisappear: functions respectively, but deinit does not get called because the (A) is still being referenced by the navigation stack. When (B) eventually pops back or gets dismissed the methods viewWillAppear: and viewDidAppear: get called (A). It's only after (A) pops that deinit gets called.
Because of this, the notifications for the keyboard are getting fired on your view controller even though it is not currently visible.
You should move your notification registration and removal to viewWillAppear: and viewDidDisappear: respectively.
Hope this helps!

NSNotificationCenter called twice in applicationDidEnterBackground when their is two view controller?

I'm using the the NSNotificationCenter.defaultCenter().postNotificationName function with the applicationDidEnterBackground function. So first I add these to AppDelegate.swift:
func applicationDidEnterBackground(application: UIApplication) {
println("applicationDidEnterBackground")
NSNotificationCenter.defaultCenter().postNotificationName("com.test.mytest", object: self)
}
And I add these to ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myTestFunc", name: "com.test.mytest", object: nil)
}
func myTestFunc () {
println("CALLED")
}
And up to now, everything is working fine, the console print out the right thing when I enter background:
applicationDidEnterBackground
CALLED
But after I add a new view controller in Storyboard and connect both of them using any of Segue:
And now when I run the app, after I clicked two buttons and then back to the home page, the applicationDidEnterBackground is still calling once but the NSNotificationCenter is called twice:
applicationDidEnterBackground
CALLED
CALLED
So how can I solve this weird problem?
EDIT
I've also tried this, but still same result:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().removeObserver(self, name: "com.test.mytest", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myTestFunc", name: "com.test.mytest", object: nil)
}
Ah, I forget the viewDidLoad is loaded twice... I solved by:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "com.test.mytest", object: nil)
}
I have experienced the same problem. In my case solution was enabling Background Modes in project capabilities tab.

Resources