I have this code, taken from this answer: https://stackoverflow.com/a/29099066/406322
extension NSNotificationCenter {
func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
}
}
Now, in my view controller, I am setting my observers in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
setObservers()
}
func setObservers() {
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(BaseController.handleComment(_:)), name: "newComment", object: nil)
}
However, even with using this extension, where the observer is removed before getting added, each time I exit the view controller, and return to it, I get multiple notifications (one extra each time).
How is this possible?
If you need this setObserver extension, you are very likely doing something wrong. You should be able to balance your registration and removal easily. If you can't, your notification management is very likely too complicated or in the wrong place.
Typically the correct place to add observations is in viewWillAppear (or viewDidAppear, either is fine), and remove them in viewDidDisappear (or viewWillDisappear). This ensures that you do not receive notifications while you are offscreen, even if the view controller still exists (which is common).
If your view controller requires that it receive notifications while it is offscreen, then you have a design problem. View controllers should only manage onscreen views. If they're doing anything else, you have put too much of the model into the controller.
As #rmaddy notes, your specific problem is likely that you have two instances of this view controller. That's may be fine or it might be a mistake (it depends on how the view controller works). But if you balance adding and removing your registration when going on and offscreen, that part will be fine.
Related
I'm working on a simple notes app for macOS. I have a home page which is has a NSTableView that displays all your notes, when you click the new note button a new View appears where you can create a new note. Once you click the note it adds the new note to the database and should reload the table view data, but I need to stop the current run and run the program again to see the changes.
I used this post to achieve the same effect on iOS but it seems to not work on MacOS
So how do I adapt:
override func viewDidLoad() {
super.viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
In the home page VC
and the line:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
Inside of the saveNewNote IBAction to work in macOS? also are you even able to use the NotificationCenter in macOS apps or is it only on iOS?
NSNotificationCenter is part of Foundation framework, so it's definitely available on macOS.
You should be able to use it the same way you've been using it on iOS.
If you have an IBAction called saveNewNote, inside that method you can posy the notification the way you wrote.
In the view controller which owns the table, add the observer like you wrote, and reload the table...
If it doesn't work, we might need some code example of how you set it up on the Mac app the better understand what isn't working.
I have two view controllers, one is a tableView with a NSFetchedResultsController and the other is a regular viewController. My tableView is properly setup with NSFetchedResultsController delegates and is working perfect. I now have realized that just because I declare the same managed object context in each separate view that the tableView/NSFetchedresultsController will not monitor changes that I have made in the regular view controller.
This is how I declare it in each view
lazy var coreDataStack = CoreDataStack(modelName: "myApp")
I understand now that I need to merge my saved changes from my regular viewControllers context to my tableView/NSFetchedresultsController context so that the delegate methods can run but I am unsure how to do so.
When I change create or update an object in the regular VC I add notification observers like this
NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: Notification.Name.NSManagedObjectContextDidSave, object: nil)
#objc func contextDidSave(_ notification: Notification) {
let frc = ConversationsViewController().fetchedResultsController
frc.managedObjectContext.mergeChanges(fromContextDidSave: notification)
frc.managedObjectContext.processPendingChanges()
print("Object did save: \(notification)")
}
After I create or update an object the regular VC, notification prints successfully but no delegate methods are called in tableView/NSFetchedresultsController. I know that the objects are saving because when I restart the app they are shown in the tableView. I would greatly appreciate if anyone can point me in the right direction as to how I can notify my fetchedResultsController an object has saved. I have seen a similar question NSFetchedResultsController doesn't call controllerDidChangeContent: after update to non-fetched NSManagedObject but it is in Objective C
I'm trying to work out the best way to update a view when the app comes into the foreground. Originally I had assumed that viewWillAppear would do the trick, but it appears I was incorrect.
I understand from other posts the correct way to do this is with the Notification Center:
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)`
So far so good; this allows me to work out when this happens correctly. However it's possible I may have several views in a stack and I don't want lots of updates happening unless the view is actually visible.
I thought the following code would allow me to do this:
#objc func willEnterForeground() {
if(self.isBeingPresented) {
updateView()
}
}
But unfortunately isBeingPresented is always false when the view is restoring from the background.
Does anybody have any suggestions on the best way to tell if the current view is the 'top' view in the stack?
I am looking for pattern in swift to synchronize the state of multiple controls. Say I have NSSegmentedControl in the view, NSMenuItems with checkmarks and NSSegmentedControl in touch bar. Each of them is sending change to model. But how to synchronize with other two? I can use delegates but it looks like the complexity will grow with number of synchronizable items. Is there a common pattern to approach this problem? I would think of something like subscription to change, so all 3 controls can subscribe to it and receive notification when a change occurs, then use it's own transformation of data to pass to the control. Wonder if there is some mechanism for that.
There is a mechanism for that, it's the notification model. I imagine you want the view controller to listen for changes and update its controls--not have each control listen for changes and update itself (which is also possible). The basic approach would be to add a notification observer to the view controller (or whatever object you want to be the observer)...
NotificationCenter.default.addObserver(self, selector: #selector(nHandler(_:)), name: NSNotification.Name(rawValue: "someControlDidChange"), object: segmentedControl)
...and a handler to take action on that notification...
#objc func nHandler(_ notification: Notification) {
guard let control = notification.object as? UISegmentedControl else {
return
}
// determine which control posted the notification
// read the control's new value
// update the other controls
}
Then as a control updates its values, it posts notifications...
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "someControlDidChange"), object: self)
The object here can be self if this notification is posted from within the control (i.e. it's subclassed) or the instance of the control itself (i.e. someSegmentedControl).
The notification observer and the notification poster allow you to include an optional object, as you see in my examples. You should be passing some data within these notifications to allow the observer to determine what object is posting the notification and what value that object has been updated to. Then in the handler of the notification observer, you unwrap that object to get that data.
I have yet to benchmark the costs of posting notifications; I really have no idea how expensive it is. If the delegate approach is too messy and you've written off property observers, the notification model is likely the way to go.
I currently use postNotification multiple times with NSTimer, but the observer is receiving it only once.
What's the way to receive the same notification multiple times without adding multiple observers?
My timer is created like this:
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(update) , userInfo: nil, repeats: true)
And inside the update method is:
let testNotification: NSNotification = NSNotification(name: "testNotification", object: self, userInfo: nil)
NSNotificationCenter.defaultCenter().postNotification(testNotification)
This is how I register the observer in one of the viewcontrollers:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(testNot), name: "testNotification", object: nil)
updateView()
}
I add the observers in a viewcontroller as one normally would.
I can confirm the timer works, because update() is being called at the regular interval, and the observer does receive the notification for the first time, but it does not recur.
Please let me know if you need to see more of the code.
I don't know how is your situation, but if is a screen for example and this screen is deallocated or dismiss and you don't set to remove all observers, most like is that is generating multiple observers for the same name, looks you receive the first one, after, another is allocated and you don't receive because they lost reference. That should help
Try recreating new object of NSNotification.
So, my testNot() function presented a modal view, and that somehow disabled further notifications from posting. Now I have to figure out how I can present a view and still keep receiving notifications. Alert views still work correctly. It throws an error in the debug console about the alert view still present when further notifications are received, but once the alert view is dismissed, everything goes back to normal.