Selector function not being entered when observer is added - swift

In my first view controller, I post the notification with the following code:
NotificationCenter.default.post(name: Notification.Name("date"), object: formattedDate)
I then "receive" the notification in a second view controller with the following code:
func receiveNotification () {
NotificationCenter.default.addObserver(self, selector: #selector(self.didGetTheDate(_:)), name: NSNotification.Name("date"), object: nil)
}
#objc
func didGetTheDate(_ notification: Notification) {
print("In did get date")
date = notification.object as! String
}
However, the function "didGetTheDate" never gets called.
I have triple checked that the function "receiveNotification" gets called as I have added print statements to check this.
Can somebody please help me with this.

NSNotificacionCenter is a variation of the Observer Pattern, you can read about it here
This means that you will have to register an Observer before posting any notification. If you post anything before that, the NSNotificationCenter will look at the observer for name and will see that nobody is listening for it, thus nothing will happen.

Related

Responding to EKEventStore Change in SwiftUI

According to the Apple Developer Documentation:
An EKEventStore object posts an EKEventStoreChanged notification whenever it detects changes to the Calendar database
The article then goes on to provide examples of how to do this in UIKit:
NotificationCenter.default.addObserver(self, selector: Selector("storeChanged:"), name: .EKEventStoreChanged, object: eventStore)
In SwiftUI, I tried to do it this way:
let publisher = NotificationCenter.default.publisher(for: NSNotification.Name("EKEventStoreChanged"))
and
.onReceive(publisher) { value in
print("Store Changed")
}
But I never received any notification, and therefore the print statement was never called.
I also tried this:
let publisher = NotificationCenter.default.publisher(for: NSNotification.Name.EKEventStoreChanged)
but nothing changed.
My Question: How do I receive the EKEventStoreChanged notification in SwiftUI?
Any help would be appreciated.

Swift selector function with optional closure?

What am I doing wrong with the #selector assignment when I add the notification observer?
NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func reloadData(completionHandler: ((Bool) -> Void)? = nil ) {
mainCollectionView.reloadData()
completionHandler?(true)
}
The app crashes whenever I post the notification:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
I have already tried things like #selector(reloadData(completionHandler: nil)
EDIT:
I have tried the selector #selector(reloadData(completionHandler:)) but it still crashes n the line where I post a notification, with the error message:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x8)
When I change the code as follows everything works fine but it is not really nice to create a function just to call another function
NotificationCenter.default.addObserver(self, selector: #selector(testCall), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func testCall() {
self.reloadData()
}
A completion handler in a notification selector method is nonsensical (and illegal). What do you expect is the object which is supposed to be the receiver of the completion handler ... ?
The syntax of a notification selector is very similar to a target/action selector. The passed parameter must be the type of the affected object (here Notification).
func reloadData(_ notification : Notification) { ...
The alternative is the block based API of Notification which is able to capture values of the enclosing function / method.
The only way to pass custom data in a notification object with specified selector is the (optional) userInfo dictionary.

Is removing a NotificationCenter observer that was created with closure syntax by name adequate?

I have a few notifications that were created using block / trailing closure syntax which look like this:
NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: moc, queue: nil) { note in
// implementation
}
Which I was later removing by name, like this:
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: moc)
My Question
Is this adequate? Or do I absolutely need to save the NSObjectProtocol to it's own property and remove that property with the following syntax?
NotificationCenter.default.removeObserver(didChangeNotification)
You absolutely need to store the return value in a property and remove that later on.
From https://developer.apple.com/reference/foundation/nsnotificationcenter/1411723-addobserverforname:
Return Value
An opaque object to act as the observer.
When you call any one of the removeObserver methods, the first parameter is the observer to remove. When you set up a block to respond to a notification, self is not the observer, NSNotificationCenter creates its own observer object behind the scenes and returns it to you.
Note: as of iOS 9, you are no longer required to call removeObserver from dealloc/deinit, as that will happen automatically when the observer goes away. So, if you're only targeting iOS 9, this may all just work, but if you're not retaining the returned observer at all, the notification could be removed before you expect it to be. Better safe than sorry.
To add to #Dave's answer, it looks like documentation isn't always 100% accurate. According to this article by Ole Begemann there is a contradiction in the doc and self-removing magic was not happening as of iOS 11.2 in his test app.
So that the answer is still "Yes, one needs to remove that observer manually" (and yes, self is not the observer, the result of addObserver() method is the observer).
Here an example with code, for how a correct implementation looks like:
Declare the variable that gets returned when you add the observer in your class A (the receiver of the notification or observer):
private var fetchTripsNotification: NSObjectProtocol?
In your init method add yourself as an observer:
init() {
fetchTripsNotification = NotificationCenter.default.addObserver(forName: .needsToFetchTrips, object: nil, queue: nil) { [weak self] _ in
guard let `self` = self else {
return
}
self.fetchTrips()
}
}
In the deinit method of your class, make sure to remove the observer:
deinit { NotificationCenter.default.removeObserver(fetchTripsNotification as Any) }
In your class B (the poster of the notification) trigger the notification like usually:
NotificationCenter.default.post(name: .needsToFetchTrips, object: nil)

Observer never called

I have two functions
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName("personalDataDidLoad", object: self, queue: NSOperationQueue.mainQueue()) {_ in
print("Received notification")
self.showPersonalData()
}
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
but for some reason this outputs
personal data loaded
instead of the expected
personal data loaded
Received notification
I'm probably missing something obvious, but I don't see it right now....
I also tried addObserver with selector: "showPersonalData:" but this throws an unrecognized selector exception..
The problem is with the 2nd parameter in postNotificationName and addObserverForName: object. When you add an observer, and pass a non-nil object value, this means that the observer block will run when a notification comes from that object, and from that object only.
However, when you fire the notification, you do object: nil. So your notification is ignored.
On the other hand, passing a nil value for object means, "I want to receive this notification regardless of who sends it".
So you need to make sure the object value is the same in both places: either self or nil.
Is there a reason you need to use addObserverForName(_:object:queue:usingBlock:)?
Try this instead:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, "personalDataDidLoadNotification:", name: "personalDataDidLoad" object: nil)
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
}
func personalDataDidLoadNotification(notification: NSNotification) {
print("notification recieved")
}
Another answer to the title question (but not this example) but will hopefully help others in the situation I have been in for the last 3 hours:
Make sure your notificationcenter observer is added inside a class that has a persisted instance. I created the observer inside a class that was called as e.g. MyClass().setupNotification inside a local method of another class.
This meant the observer was immediately deleted and didnt persist against any instance.
Schoolboy error - but hope this helps others searching for this.

NSNotification never being sent

I'm trying to use the NSNotificationCenter with Swift and I'm running into a problem. I'm trying the following:
class MyClass {
init() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopTimer", name: UIApplicationDidEnterBackgroundNotification, object: [])
}
func stopTimer() {
println("Entered background!")
}
}
When hitting the home button, the observer is never calling the selector, therefore my message is never being printed out.
Notice that I send an empty object at the end, the method signature for addObserver is expecting an implicitly unwrapped object and setting it to nil results in an EXC_BAD_ACCESS error when the app enters background.
Any ideas?
EDIT
I forgot to mention that this code is being executed from a framework included in my project.
Executing the code gives me a bad access abort signal.
You should be able to pass nil for an optional argument. Can you elaborate more on what happens if you do? This is working for me:
func init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopTimer", name: UIApplicationDidEnterBackgroundNotification, object: nil)
}
func stopTimer() {
println("Entered background!")
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Alright, I figured this out. 2 problems:
There was no strong reference to the instance of my class in my app
My class wasn't extending NSObject
Now, I can set object: nil and everything works fine.
Thanks for all your help! :)
Perhaps you should try implementing applicationDidEnterBackground(application: UIApplication) instead?
To further troubleshoot, you may also want to add a log statement to your app delegate's deinit to see if the delegate gets deallocated after entering the background.