Filip Němeček
Posted on February 15, 2020
Let’s say you have somewhat bigger app and need to update its after user changes settings. Maybe they selected new ordering for list items, changed language or what not. Or maybe you need to display notifications badge on multiple screens.
Some of these challenges could be solved with delegates or closures. But you would need to properly connect all these parts and the result would be tightly coupled. Your list controller would need to know about settings controller and vice versa to be notified about changes.
Fortunately iOS provides pretty neat solution called NotificationCenter
. This has nothing to do with push notifications or any other kind of notifications the user might see. Instead it is a way to propagate changes and send data across your application.
When you need to originate change you will use NotificationCenter
to post a notification and that is the first part done. The second part is to set observers for specific notifications when you need to know about something happening.
Basic NotificationCenter
usage
Imagine we want to change list (or rather TableView) ordering on multiple screens after user changes preference in settings screen.
In the settings controller, we would have method like this:
func saveOrderingPreference(new preference: String) {
UserDefaults.standard.set(preference, forKey: "orderingPreference")
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "orderingPreferenceChanged")))
}
In real code we would have enum
for preferences and constant for UserDefaults
key but this is an example :-)
The second line will use default NotificationCenter
to post notification. It does not care whether anyone is listening.
Extension for notification constants
You may spot the issue with the Notification
object construction which is not that pretty. Fortunately we can create extension
to provide constants for notifications:
extension Notification.Name {
static let orderingPreferenceChanged = Notification.Name("orderingPreferenceChanged")
}
I recommend using fairly long and descriptive names for notifications so they don’t have chance to clash.
Now the post
method call looks much better:
NotificationCenter.default.post(name: .orderingPreferenceChanged, object: nil)
Another advantage is that with constant we don’t have to worry about misspelling the name.
Now let’s subscribe to our new notification:
NotificationCenter.default.addObserver(self, selector: #selector(onOrderingPreferenceChanged), name: .orderingPreferenceChanged, object: nil)
This is typically done in viewDidLoad
in case of a view controller. You have the name
of the notification and #selector
to mark method to execute when notification is received.
@objc func onOrderingPreferenceChanged() {
// reload data
}
And this is basic example finished. However there are few things you should know about NotificationCenter
.
Few things to note
The method selected in observer may not be executed on the main thread which will mean problems for UI related stuff like tableView.reloadData()
. It is best to always use DispatchQueue.main.async
when manipulating UI like so:
@objc func onOrderingPreferenceChanged() {
DispatchQueue.main.async {
// reload data
}
}
Correctly handling incoming notification should be the receiver’s responsibility so posting notifications with DispatchQueue
is not a solution.
Also NotificationCenter
work synchronously, meaning all observers have to finish work for you code after post
call to continue.
You don’t have to use NotificationCenter.default
, you can create your own. This can be useful during testing or when you want to have absolute certainty that no other code (not even OS) is sending and receiving notifications.
How to send data with NotificationCenter
Notification center uses the userInfo
design pattern which you can leverage to send simple data with your notification. This could be id of item to act upon or any other primitive type.
We would add userInfo
to post
like this:
NotificationCenter.default.post(name: .orderingPreferenceChanged, object: nil, userInfo: ["preference”: new])
And then modify our method signature to have Notification
parameter:
@objc func onOrderingPreferenceChanged(notification: Notification) {
}
Don’t forget to change the addObserver
:
NotificationCenter.default.addObserver(self, selector: #selector(onOrderingPreferenceChanged(notification:)), name: .orderingPreferenceChanged, object: nil)
And access the sent data:
@objc func onOrderingPreferenceChanged(notification: Notification) {
if let userInfo = notification.userInfo, let preference = userInfo[“preference”] as? String {
}
}
Thanks for reading!
Is anything not clear? Do you want more information? Ask in the comments and I will do my best to help you. Thanks for reading!
Posted on February 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.