NotificationCenter Crash in Swift 3 - ios

Is it just me, or did NotificationCenter become a hot mess in Swift 3? :)
I have the following setup:
// Yonder.swift
extension Notification.Name {
static let preferenceNotification = Notification.Name("preferencesChanged")
}
// I fire the notification elsewhere, like this:
NotificationCenter.default.post(name: .preferenceNotification, object: nil)
In my first view controller, this works great:
// View Controller A <-- Success!
NotificationCenter.default.addObserver(self, selector: #selector(refreshData), name: .preferenceNotification, object: nil)
func refreshData() {
// ...
}
But this view controller:
//View Controller B <-- Crash :(
NotificationCenter.default.addObserver(self, selector: #selector(loadEntries(search:)), name: .preferenceNotification, object: nil)
func loadEntries(search:String?) {
// ...
}
...crashes with:
[NSConcreteNotification length]: unrecognized selector sent to instance
As far as I can tell, my observer is set up correctly. Any idea what I'm doing wrong?

Your issue is with your loadEntries(search:) method. It's not a valid signature. The selector used with Notification Center must either have no parameters or just one parameter. And if you have one parameter, that parameter will be the Notification object, not the notification name.
Your loadEntries needs to be:
func loadEntries(_ notification: NSNotification) {
// Optional check of the name
if notification.name == .preferenceNotification {
}
}
And the selector would need to be:
#selector(loadEntries(_:)) // or #selector(loadEntries)

Related

-[NSConcreteNotification count]: unrecognized selector sent to instance 0x2816dd380

Took me an hour to figure this iOS crash out, posting the solution in case it helps.
You might have a different value other than count after NSConcreteNotification in your crash.
I was crashing on accessing an array.count, which is why it's count in my case:
#objc fileprivate func loadParts(constraints: [NSLayoutConstraint]? = nil) {
assert(Thread.current.isMainThread)
var constraints = constraints ?? [NSLayoutConstraint]()
...
let cCount = constraints.count
I could not for the life of me see how it could crash on constraints.count as the array is guaranteed to exist.
I was wiring this function up to a Notification like this:
NotificationCenter.default.addObserver(self, selector: #selector(self.loadParts), name: UIDevice.batteryStateDidChangeNotification, object: nil)
If you look at the documentation for addObserver, it says the function must have exactly one parameter which is a Notification. What was happening was that my function was being called with a Notification, but my code expected it to be an array.
The fix was to create a new function that simply called the function I wanted (loadParts), and have the Notification hit that instead:
NotificationCenter.default.addObserver(self, selector: #selector(self.loadPartsNotification(_:)), name: UIDevice.batteryStateDidChangeNotification, object: nil)
...
#objc fileprivate func loadPartsNotification(_ notification: Notification) {
self.loadParts()
}
fileprivate func loadParts(constraints: [NSLayoutConstraint]? = nil) {
...

Must Selector method be in the same instances as where the observer is?

Normally, the code addObserver both the Selector method tag with #Objc are coded in the same instance (instantiated class).
It is possible to pass a Selector from different instance to the addObserver?
The reason for doing this is because Selector behavior as a callback most of the time. Some of the callback methods are commonly used and could well be coded into a CommonCallBack Class, an example of usage would be like this:
class SomeViewController{
override func viewDidLoad() {
...
let common = CommonCallback()
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
class CommonCallback{
#Objc func methodA() {
// doing A
}
}
The issue is I keep getting unrecognized selector sent to instance
You can also achieve this by doing this way
class SomeViewController {
override func viewDidLoad() {
let common = CommonCallback()
common.enableObserver = true
}
}
class CommonCallback{
var enableObserver : Bool!
override func viewDidLoad() {
if enableObserver {
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
#objc func methodA() {
// Your code here
}
}
I found out, where went wrong if the code. The observer argument must be in the same class as the Selector method, so instead of:
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
it should be:
NotificationCenter.default.addObserver(
common,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
This will call CommonCallback.methodA instead of SomeViewController.methodA

Notification Center Observer Selector Method does not get called

I am adding an observer on A view controller and view controller B is presenting on A.
While dismissing Controller B , I have posted the notification but It does not called the selector method added in A.
Also notification get registered first and the post method is get called. I have already checked that.
Here is the sample code:
NotificationCenter.default.addObserver(self, selector: #selector(closButtonPressed(notification:)) ,name: Notification.Name("CloseButtonPressed"), object: nil)
#objc func closButtonPressed(notification: Notification){
}
NotificationCenter.default.post(name: Notification.Name("CloseButtonPressed"), object: self)
Any help would be appreciated.
Make sure posting notification in completion handler
self?.dismiss(animated: true, completion: {
NotificationCenter.default.post(name: Notification.Name("CloseButtonPressed"),
object: self)
}
Make sure you implement Notification correctly. I recommend you to create an extension for Notification Name & make it static.
extension Notification.Name {
static let didTapCloseButton = Notification.Name("CloseButtonPressed")
}
NotificationCenter.default.addObserver(self, selector: #selector(didTapCloseButton(_:)), name: .didTapCloseButton, object: nil)
#objc func didTapCloseButton(_ sender: Notification?) {
}
NotificationCenter.default.post(name: .didTapCloseButton, object: nil)
I am not sure what's wrong with your project. To solve your problem, I create a test project and write some code like this:
//ControllerA.swift
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(getNotification(notification:)), name: NSNotification.Name("CloseButtonPressed"), object: nil)
}
getNotification(notification: Notification) {
print(notification)
}
#objc func buttonAClick() {
navigationController?.present(ViewControllerB(), animated: true, completion: {
})
}
//ViewControllerB.swift
#objc func buttonClick() {
NotificationCenter.default.post(name: Notification.Name("CloseButtonPressed"), object: self)
self.dismiss(animated: true) {
}
}
As you said, I add notification in ControllerA, and present ControllerB, When ControllerB close, post notification and dismiss, console can print the notification object, so may I miss something?

How to register Notification Center in class iOS

I am new to ios programming, I wanted to register notification center in the class, not in the view controller, I want to send some action from one view controller this custom class receives that action and it performs some view controller navigation.I wanted to have custom notification center in ios.
code:
NotificationCenter.default.post(name: Notification.Name(rawValue: "key"), object: nil)
class MainReceiver: NotificationCenter
{
override func post(name aName: NSNotification.Name, object anObject: Any?)
{
print("coming here")
}
}
Here you have snipped code:
class MainReceiver {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleMethod(_:)), name: NSNotification.Name(rawValue: "Notification_Name"), object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func handleMethod(_ notification: Notification) {
// handle
}
}
From Apple's NSNotificationCenter documentation:
Each running Cocoa program has a default notification center. You typically don’t create your own.
I'm not sure why you think you want to create your own notification center, but trust me, you don't.
However, you can easily subscribe an object (an instance of a custom class, like you say you want to use) to notifications:
// let object = Whatever()
NotificationCenter.default.addObserver(object, selector: #selector(didReceive(notification:)), name: NSNotification.Name(rawValue: "key"), object: nil)
// object's didReceive(notification:) function will now be called when a notification with the specified name is posted. This can be posted from anywhere.
A while back, the function had to be decorated with the #objc attribute. I'm not sure if that's still the case, but if so, you'd use #objc func didReceive(notification: Notification) as your declaration in the receiving class.
If I understand your question correctly I think what you are looking for is the blocked based variant for the NotificationCenter.
You can find it in the official docs.
Example code from the docs:
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: \(NSLocale.currentLocale().localeIdentifier)")

NSNotification issue - unrecognized selector sent to instance

I have this observer
NotificationCenter.default.addObserver(self, selector: #selector(flashButtonDidPress(_:)), name: NSNotification.Name(rawValue: "flash"), object: nil)
And this delegate function
func flashButtonDidPress(_ title: String) {
cameraController.flashCamera(title)
}
Can someone explain me why I have the following error?
unrecognized selector sent to instance
Thanks in advance
EDIT:
I am also accessing the function without the use of a notification
NotificationCenter sends Notifications, not Strings, use a second function to be called from somewhere else:
func flashButtonDidPress(_ notification: Notification) {
if let title = notification.userInfo?["title"] as? String {
flashCamera(with:title)
}
}
func flashCamera(with title: String)
{
cameraController.flashCamera(title)
}
pass the title in the userInfo dictionary when posting the notification, e.g.
let userInfo = ["title", title]

Resources