133

In Objective-C, a custom notification is just a plain NSString, but it's not obvious in the WWDC version of Swift 3 just what it should be.

hexdreamer
  • 1,779
  • 2
  • 12
  • 11

13 Answers13

426

There is a cleaner (I think) way to achieve it

extension Notification.Name {

    static let onSelectedSkin = Notification.Name("on-selected-skin")
}

And then you can use it like this

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
ThomasW
  • 16,981
  • 4
  • 79
  • 106
Cesar Varela
  • 5,004
  • 2
  • 16
  • 17
  • 2
    I'm using the code above. This is a static property. – Cesar Varela Sep 02 '16 at 05:24
  • 3
    Very clean, I like it alot – 0xT0mT0m Oct 09 '16 at 06:13
  • 10
    `extension NSNotification.Name` instead of `extension Notification.Name `. Otherwise Swift 3 complaints with `'Notification' is ambiguous for type lookup in this context` – lluisgh Oct 25 '16 at 16:05
  • 9
    You get my upvote for making a typo in the string and thus demonstrating the value of typed notification names :P – Dorian Roy Feb 07 '17 at 16:07
  • 10
    It might be worth noting that this is the method suggested by Apple in WWDC 2016 Session 207 https://developer.apple.com/videos/play/wwdc2016/207/ – Leon Apr 26 '17 at 14:11
  • If using `Notification.Name` as opposed to the Objective-C `NSNotification.Name` you should note that `NotificationCenter.post` now expects an object of type `Notification`. Posting looks like this: `NotificationCenter.default.post(Notification(.onSelectedSkin, object: selectedSkin))`. May appear more verbose, but it does make more sense verb-wise to post a `Notification` directly rather than a name of a notification. Not sure what version of Swift this started in, but I'm still in 3.2. – shim Oct 02 '17 at 22:00
  • @lluisgh no extra errors in Swift4, `extension Notification.Name` is enough – Kamil Harasimowicz Feb 04 '18 at 10:36
  • 2
    Nice, although I guess the string names have the risk of redundant values. – Jonny Nov 01 '18 at 06:44
  • I had to extend *NSNotification.Name* in order to achieve this with Swift 5. – nickdnk Jul 25 '20 at 22:04
39

You could also use a protocol for this

protocol NotificationName {
    var name: Notification.Name { get }
}

extension RawRepresentable where RawValue == String, Self: NotificationName {
    var name: Notification.Name {
        get {
            return Notification.Name(self.rawValue)
        }
    }
}

And then define your notification names as an enum anywhere you want. For example:

class MyClass {
    enum Notifications: String, NotificationName {
        case myNotification
    }
}

And use it like

NotificationCenter.default.post(name: Notifications.myNotification.name, object: nil)

This way the notification names will be decoupled from the Foundation Notification.Name. And you will only have to modify your protocol in case the implementation for Notification.Name changes.

halil_g
  • 631
  • 6
  • 11
  • This is exactly they way I originally thought it should work - notifications should be enums. Thanks for the trick! – hexdreamer Feb 22 '17 at 18:25
  • No problem! I edited the code to include conformation of the extension to `NotificationName` so the `name` property is only added to the enums that conform to the protocol. – halil_g Feb 24 '17 at 16:58
  • 2
    Strictly equivalent but more logical IMO, you can define the extension on NotificationName (instead of RawRepresentable) like this: `extension NotificationName where Self: RawRepresentable, Self.RawValue == String {` – jlj Mar 03 '17 at 09:30
37

Notification.post is defined as:

public func post(name aName: NSNotification.Name, object anObject: AnyObject?)

In Objective-C, the notification name is a plain NSString. In Swift, it's defined as NSNotification.Name.

NSNotification.Name is defined as:

public struct Name : RawRepresentable, Equatable, Hashable, Comparable {
    public init(_ rawValue: String)
    public init(rawValue: String)
}

This is kind of weird, since I would expect it to be an Enum, and not some custom struct with seemingly no more benefit.

There is a typealias in Notification for NSNotification.Name:

public typealias Name = NSNotification.Name

The confusing part is that both Notification and NSNotification exist in Swift

So in order to define your own custom notification, do somethine like:

public class MyClass {
    static let myNotification = Notification.Name("myNotification")
}

Then to call it:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
hexdreamer
  • 1,779
  • 2
  • 12
  • 11
  • 3
    Good answer. Some comments: *This is kind of weird, since I would expect it to be an Enum* — An enum is a *closed* set. If `Notification.Name` were an enum, nobody would be able to define new notifications. We use structs for otherwise-enum-like types that need to allow adding new members. (See the [swift-evolution proposal](https://github.com/apple/swift-evolution/blob/master/proposals/0033-import-objc-constants.md).) – rickster Jun 18 '16 at 19:21
  • 2
    *The confusing part is that both Notification and NSNotification exist in Swift* — `Notification` is a value type (a struct), so that it can benefit from Swift's semantics for value (im)mutability. Generally, Foundation types are dropping their "NS" in Swift 3, but where one of the new Foundation Value Types exists to supplant it, the old reference type sticks around (keeping the "NS" name) so that you can still use it when you need reference semantics or to subclass it. See the [proposal](https://github.com/apple/swift-evolution/blob/master/proposals/0069-swift-mutability-for-foundation.md). – rickster Jun 18 '16 at 19:26
  • Let me clarify: I expect notification names to be enums, like Errors are. You can define your own Error enums, and make them conform to ErrorType. – hexdreamer Jun 19 '16 at 21:13
  • 1
    True — Apple could at least theoretically have made NotoficationName (or some such) a protocol, to which you create conforming types. I dunno, but there's likely a reason they didn't... Probably something to do with ObjC bridging? File a bug (to [open source](http://bugs.swift.org), Foundation Swift is in the open) if you've got a better solution worked out. – rickster Jun 22 '16 at 04:41
  • In your solution you capitalize the declaration of `MyNotification`. Apple's rewrite of their notifications does the same thing. Any idea why this is? As far as I can tell the Swift 3 style guide only suggests capitalizing for protocols and classes. But these are static properties. – Sean G Aug 23 '16 at 18:07
  • 2
    You are probably correct in that it should begin with lowercase. – hexdreamer Aug 25 '16 at 02:01
13

Easier way:

let name:NSNotification.Name = NSNotification.Name("notificationName")
NotificationCenter.default.post(name: name, object: nil)
Zoltan Varadi
  • 2,468
  • 2
  • 34
  • 51
13

I may suggest another option which is similar to what @CesarVarela suggested.

extension Notification.Name {
    static var notificationName: Notification.Name {
        return .init("notificationName")
    }
}

This will let you post and subscribe on notifications easily.

NotificationCenter.default.post(Notification(name: .notificationName))

Hope this will help you.

Mikhail Glotov
  • 336
  • 3
  • 9
11

You can add a custom initializer to NSNotification.Name

extension NSNotification.Name {
    enum Notifications: String {
        case foo, bar
    }
    init(_ value: Notifications) {
        self = NSNotification.Name(value.rawValue)
    }
}

Usage:

NotificationCenter.default.post(name: Notification.Name(.foo), object: nil)
efremidze
  • 2,640
  • 1
  • 25
  • 35
  • 1
    Lower case 'enum type' and 'init(_ type: type)' for Swift 3.0.2 – Jalakoo Jan 26 '17 at 19:45
  • @Jalakoo Only the `case`s in an enum should be lowercased, not the enum itself. Type names are uppercased, and enums are types. – manmal May 30 '17 at 13:12
4
NSNotification.Name(rawValue: "myNotificationName")
shim
  • 9,289
  • 12
  • 69
  • 108
Lee Probert
  • 10,308
  • 8
  • 43
  • 70
4

I did my own implementation mixing things from there and there, and find this as the most convenient. Sharing for who any that might be interested:

public extension Notification {
    public class MyApp {
        public static let Something = Notification.Name("Notification.MyApp.Something")
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.onSomethingChange(notification:)),
                                               name: Notification.MyApp.Something,
                                               object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @IBAction func btnTapped(_ sender: UIButton) {
        NotificationCenter.default.post(name: Notification.MyApp.Something,
                                      object: self,
                                    userInfo: [Notification.MyApp.Something:"foo"])
    }

    func onSomethingChange(notification:NSNotification) {
        print("notification received")
        let userInfo = notification.userInfo!
        let key = Notification.MyApp.Something 
        let something = userInfo[key]! as! String //Yes, this works :)
        print(something)
    }
}
inigo333
  • 3,088
  • 1
  • 36
  • 41
2

This is just reference

// Add observer:
NotificationCenter.default.addObserver(self,
    selector: #selector(notificationCallback),
    name: MyClass.myNotification,
    object: nil)

    // Post notification:
    let userInfo = ["foo": 1, "bar": "baz"] as [String: Any]
    NotificationCenter.default.post(name: MyClass.myNotification,
        object: nil,
        userInfo: userInfo)
Ori Dar
  • 18,687
  • 5
  • 58
  • 72
1

The advantage of using enums is that we get the compiler to check that the name is correct. Reduces potential issues and makes refactoring easier.

For those who like using enums instead of quoted strings for notification names, this code does the trick:

enum MyNotification: String {
    case somethingHappened
    case somethingElseHappened
    case anotherNotification
    case oneMore
}

extension NotificationCenter {
    func add(observer: Any, selector: Selector, 
             notification: MyNotification, object: Any? = nil) {
        addObserver(observer, selector: selector, 
                    name: Notification.Name(notification.rawValue),
                    object: object)
    }
    func post(notification: MyNotification, 
              object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) {
        post(name: NSNotification.Name(rawValue: notification.rawValue), 
             object: object, userInfo: userInfo)
    }
}

Then you can use it like this:

NotificationCenter.default.post(.somethingHappened)

Though unrelated to the question, the same can be done with storyboard segues, to avoid typing quoted strings:

enum StoryboardSegue: String {
    case toHere
    case toThere
    case unwindToX
}

extension UIViewController {
    func perform(segue: StoryboardSegue) {
        performSegue(withIdentifier: segue.rawValue, sender: self)
    }
}

Then, on your view controller, call it like:

perform(segue: .unwindToX)
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
Eneko Alonso
  • 18,884
  • 9
  • 62
  • 84
  • > `NotificationCenter.default.post(.somethingHappened)` This throws an error; the methods you added in your extension accept more arguments. –  Jun 25 '18 at 07:18
1

@CesarVarela's answer is good, but to make the code slightly cleaner, you can do the following:

extension Notification.Name {
    typealias Name = Notification.Name

    static let onSelectedSkin = Name("on-selected-skin")
    static let onFoo = Name("on-foo")
}
ThomasW
  • 16,981
  • 4
  • 79
  • 106
1

If you want this to work cleanly in a project that uses both Objective-C and Swift at the same time, I found it to be easier to create the notifications in Objective-C.

Create an .m/.h file:

//CustomNotifications.h
#import <Foundation/Foundation.h>

// Add all notifications here
extern const NSNotificationName yourNotificationName;
//CustomNotifications.m
#import "CustomNotifications.h"

// Add their string values here
const NSNotificationName yourNotificationName = @"your_notification_as_string";

In your MyProject-Bridging-Header.h (named after your project) to expose them to Swift.

#import "CustomNotifications.h"

Use your notifications in Objective-C like this:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yourMethod:) name:yourNotificationName:nil];

And in Swift (5) like this:

NotificationCenter.default.addObserver(self, selector: #selector(yourMethod(sender:)), name: .yourNotificationName, object: nil)
nickdnk
  • 4,010
  • 4
  • 24
  • 43
0

if you use string-only custom notifications, there's no reason to extend any classes but String

    extension String {
        var notificationName : Notification.Name{
            return Notification.Name.init(self)
        }
    }
Quang Vĩnh Hà
  • 494
  • 3
  • 8