Update 9/18/16: Swift 3.0 has brough even more changes to working with notifications and userInfo
; come visit our new article on handling Notification
s with Swift 3.0
Update: Swift 2.2 has brought a number of changes; come visit our new article on handling NSNotification userInfo
in Swift.
I frequently sit down intent on writing about one topic, only finding myself in a situation where I feel motivated to write about something else. This is one such post.
This morning I started with working on a HomeKit application, only to take a detour to working with NSNotifications in Swift. I’m going to make the assumption that you are already familiar with both NSNotification
and NSNotificationCenter
. I tend to use these a lot when programming applications that have a number of moving parts and threads. I may be doing it all wrong and I’m sure there’s some pattern I’m missing out on, but the model of broadcasting events (notifications) to interested parties has served me well.
Let’s look at the basic Swift method for getting a handle to the default notification center (I’m going to break my verbosity rule here and use nc
as the variable name rather than notificationCenter
):
1 |
let nc = NSNotificationCenter.defaultCenter() |
Simple enough. Now, I want to register my object (typically a view controller) for receiving a notification.
1 |
nc.addObserver(self, selector: "addHomeError", name: kAddHomeError, object: nil) |
In plain English, I’m telling the notification center to call my function addHomeError
whenever someone posts a kAddHomeError
notification. self
is the object to call and addHomeError
is the function. Notice that the Objective-C @selector()
construct is gone in favor of naming the function as a string. And of course kAddHomeError
is simply a constant string defined somewhere else. In this case the definition is let kAddHomeError = "AddHomeError"
.
In another object I can post my notification with:
1 |
nc.postNotificationName(kAddHomeError, object: nil) |
Since my first object registered to receive a notification when kAddHomeError
was posted, its addHomeError
function will be called.
In both cases I’ve left the object
parameter nil
. This parameter is applicable only if you want to distinguish between separate objects registering and posting notifications. When specifying nil
in the addObserver
function you are saying, “Hey, I don’t care who posts this notification, send it to me.” If you provide an object to the object
parameter you are making a statement, “I’m only interested in notifications from this specific object.”
In the above example our function we want called is addHomeError
, so we need to write it:
1 2 3 4 5 |
func addHomeError() -> Void { var alert = UIAlertController(title: "HomeKit Wizard", message: "Unable to add home", preferredStyle: UIAlertControllerStyle.Alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } |
Given that I’m calling presentViewController
in our function, it’s a safe bet to assume that I’m handling receipt of the notification in a view controller. So our notification comes from one object, and is handled in a view controller. Straightforward. What I don’t like though is the error message is Unable to add home. It would be nicer if the dialog indicated the name of the home it couldn’t add.
We can accomplish that through the use of the userInfo
parameter when calling postNotificationName
. It took a minute to figure out the function signature, because quite frankly it looks a little, well, non-obvious. Let’s take a look.
1 2 3 |
func postNotificationName(_ notificationName: String, object notificationSender: AnyObject?, userInfo userInfo: [NSObject : AnyObject]?) |
Perhaps you’re a quicker study than I am, but… [NSObject : AnyObject]?
. Huh? What? (No pun intended.)
A glance back at the Objective-C signature for postNotificationName
and the lightbulb went off. Or at least started glowing a little. The userInfo
parameter in Objective-C is an NSDictionary
and it stands to reason Swift would be no different. And it isn’t. The signature syntax just looks strange. Seriously though, all you need to do is pass in a dictionary where the keys are derived from NSObject
and the values are AnyObject
. The question mark is just there to signify that this parameter could be nil.
Unlike earlier versions of Objective-C ([NSDictionary dictionaryWithObjectsAndKeys:]
anyone?), Swift comes out of the box with so-called “literal dictionaries,” a construct that other languages such as Python and Ruby call just dictionaries. So we don’t have to do any special dictionary-building to use userInfo
. Our error notification can now be transformed into:
1 2 3 |
nc.postNotificationName(kAddHomeError, object:nil, userInfo:["message":"Unable to add \(homeName) Home"]) |
We don’t have to limit ourself to a single-entry dictionary either:
1 2 3 4 |
nc.postNotificationName(kAddHomeError, object:nil, userInfo:["message":"Unable to add \(homeName) Home", "details":e.localizedDescription]) |
Now, let’s handle our notification. The first thing we need to do is update our function to be passed the notification object which contains our userInfo
. Recall the first version of the function had no arguments, that is, we didn’t care about the NSNotification
object itself.
Our method signature becomes func addHomeError(notification:NSNotification)
. If we made this change now and ran our application it would most surely crash. Why? Because of this right here:
. That selector definition indicates a function named
nc.addObserver(self, selector: "addHomeError", name: kAddHomeError, object: nil)addHomeError
which takes no arguments. We don’t have that anymore, we take one argument. So you’re in for a unrecognized selector sent to instance
crash if you try that. Update the selector to addHomeError:
:
1 |
nc.addObserver(self, selector: "addHomeError:", name: kAddHomeError, object: nil) |
Finally, in our addHomeError
function, let’s unwrap our userInfo
dictionary and message.
1 2 3 4 5 6 7 8 9 |
func addHomeError(notification:NSNotification) { let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!> let messageString = userInfo["message"] var alert = UIAlertController(title: "HomeKit Wizard", message: messageString, preferredStyle: UIAlertControllerStyle.Alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } |
Now perhaps there’s an easier way to do this, but, when receiving the userInfo
, the routine handling the notification doesn’t know what type of dictionary is there. Recall that the only requirement was that it be of type [NSObject : AnyObject]?
. So our code has to be a bit more explicit as what we know the dictionary to be, and that is of type [String : String!]
. Once we’ve set the compiler straight, we obtain our messageString
and supply it to our UIAlertController
.
Now we get a nice error message:
With that out of the way I can get back to our upcoming article on HomeKit.
Thank you for sharing the customizable notification example. Its always so easy to learn by example.
btw, user info receive strong variables, and if you want to put value to string key, for example, you need to set value with exclamation mark.
let value = "42"
NSNotificationCenter.defaultCenter().postNotificationName("SomeNotificationName", object: nil, userInfo: ["mainAnswer":value!])
Very nice! Helped me a lot – thank you!
Question: How did you learn Swift and Xcode? I’m currently doing a “build my own app so Google stuff” crash course, but wondered if you could recommend a better way.
Alex, you’re welcome. For learning Xcode and iOS application development in general I recommend the Big Nerd Ranch iOS Programming book. For Swift start with Apple’s Swift Reference.
Awesome – I’ll check it out! Thanks!
Thank you so much! The part that was killing me was that you need the colon after the selector method name if you have parameters. If anyone else has the same issue, I hope this comment helps!
Super. Solved my issues perfectly. What’s the theory behind the “:” on the name?
The “:” colon comes from Objective-C selector signature. Take a look at Stack Overflow, second answer down.
Hi. I’m not sure if this is relevant to the post, but I can’t find elsewhere that would answer our question. For our app, we would like the user to be able to input information. Can we do that using the userInfo displayed here?