hexdreamer
hexdreamer

Reputation: 1799

How do you create custom notifications in Swift 3?

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.

Upvotes: 134

Views: 58199

Answers (13)

nickdnk
nickdnk

Reputation: 4300

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)

Upvotes: 1

Mikhail Glotov
Mikhail Glotov

Reputation: 376

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.

Upvotes: 13

ThomasW
ThomasW

Reputation: 17307

@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")
}

Upvotes: 1

Cesar Varela
Cesar Varela

Reputation: 5114

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)

Upvotes: 429

Eneko Alonso
Eneko Alonso

Reputation: 19662

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)

Upvotes: 1

efremidze
efremidze

Reputation: 2842

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)

Upvotes: 11

Quang Vĩnh H&#224;
Quang Vĩnh H&#224;

Reputation: 514

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)
        }
    }

Upvotes: 0

inigo333
inigo333

Reputation: 3396

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)
    }
}

Upvotes: 4

Lee Probert
Lee Probert

Reputation: 10839

NSNotification.Name(rawValue: "myNotificationName")

Upvotes: 4

halil_g
halil_g

Reputation: 631

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.

Upvotes: 39

Zoltan Varadi
Zoltan Varadi

Reputation: 2478

Easier way:

let name:NSNotification.Name = NSNotification.Name("notificationName")
NotificationCenter.default.post(name: name, object: nil)

Upvotes: 13

user6943269
user6943269

Reputation: 29

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)

Upvotes: 2

hexdreamer
hexdreamer

Reputation: 1799

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)

Upvotes: 37

Related Questions