Wie erstellen Sie benutzerdefinierte Benachrichtigungen in Swift 3?

Antworten:

32

Sie können hierfür auch ein Protokoll verwenden

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

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

Und definieren Sie dann Ihre Benachrichtigungsnamen als eine enumbeliebige Stelle. Beispielsweise:

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

Und benutze es gerne

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

Auf diese Weise werden die Benachrichtigungsnamen von der Stiftung entkoppelt Notification.Name. Und Sie müssen Ihr Protokoll nur ändern, wenn die Implementierung fürNotification.Name Änderungen vorsieht.

halil_g
quelle
Genau so dachte ich ursprünglich, dass es funktionieren sollte - Benachrichtigungen sollten Aufzählungen sein. Danke für den Trick!
Hexdreamer
Kein Problem! Ich habe den Code so bearbeitet, dass er die Konformation der Erweiterung enthält, NotificationNamesodass die nameEigenschaft nur zu den Aufzählungen hinzugefügt wird, die dem Protokoll entsprechen.
halil_g
Streng äquivalent, aber logischer IMO, können Sie die Erweiterung auf NotificationName (anstelle von RawRepresentable) wie folgt definieren:extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj
386

Es gibt einen saubereren (glaube ich) Weg, dies zu erreichen

extension Notification.Name {

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

Und dann können Sie es so verwenden

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
Cesar Varela
quelle
2
Ich benutze den obigen Code. Dies ist eine statische Eigenschaft.
Cesar Varela
3
Sehr sauber, ich mag es sehr
Tom Wolters
10
extension NSNotification.Name statt extension Notification.Name . Ansonsten Swift 3 Beschwerden mit'Notification' is ambiguous for type lookup in this context
lluisgh
9
Sie erhalten meine Gegenstimme dafür, dass Sie einen Tippfehler in der Zeichenfolge gemacht und damit den Wert getippter Benachrichtigungsnamen demonstriert haben: P
Dorian Roy
10
Es könnte erwähnenswert sein, dass dies die von Apple in WWDC 2016 Session 207 vorgeschlagene Methode ist. Developer.apple.com/videos/play/wwdc2016/207
Leon
36

Notification.post ist definiert als:

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

In Objective-C ist der Benachrichtigungsname ein einfacher NSString. In Swift ist es als NSNotification.Name definiert.

NSNotification.Name ist definiert als:

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

Dies ist etwas seltsam, da ich erwarten würde, dass es sich um eine Aufzählung handelt und nicht um eine benutzerdefinierte Struktur, die anscheinend keinen Nutzen mehr hat.

In Notification for NSNotification.Name gibt es einen Typealias.

public typealias Name = NSNotification.Name

Der verwirrende Teil ist, dass sowohl Benachrichtigung als auch NS-Benachrichtigung in Swift vorhanden sind

Um Ihre eigene benutzerdefinierte Benachrichtigung zu definieren, gehen Sie wie folgt vor:

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

Dann um es zu nennen:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
Hexdreamer
quelle
3
Gute Antwort. Einige Kommentare: Das ist irgendwie komisch, da ich erwarten würde, dass es eine Aufzählung ist - Eine Aufzählung ist eine geschlossene Menge. Wenn Notification.Namees sich um eine Aufzählung handelt, kann niemand neue Benachrichtigungen definieren. Wir verwenden Strukturen für ansonsten enumähnliche Typen, die das Hinzufügen neuer Mitglieder ermöglichen müssen. (Siehe den Vorschlag zur schnellen Entwicklung .)
Rickster
2
Der verwirrende Teil ist, dass sowohl Benachrichtigung als auch NSNotifizierung in Swift vorhanden sind - Notificationist ein Werttyp (eine Struktur), so dass er von Swifts Semantik für Wert (im) Mutabilität profitieren kann. Im Allgemeinen löschen Foundation-Typen ihre "NS" in Swift 3, aber wenn einer der neuen Foundation-Werttypen vorhanden ist, um ihn zu ersetzen, bleibt der alte Referenztyp erhalten (wobei der Name "NS" beibehalten wird), sodass Sie ihn weiterhin verwenden können, wenn Sie benötigen eine Referenzsemantik oder eine Unterklasse. Siehe den Vorschlag .
Rickster
Lassen Sie mich klarstellen: Ich erwarte, dass Benachrichtigungsnamen Aufzählungen sind, wie dies bei Fehlern der Fall ist. Sie können Ihre eigenen Fehleraufzählungen definieren und sie an ErrorType anpassen.
Hexdreamer
1
Richtig - Apple hätte NotoficationName (oder ein solches) zumindest theoretisch zu einem Protokoll machen können, für das Sie konforme Typen erstellen. Ich weiß nicht, aber es gibt wahrscheinlich einen Grund, warum sie es nicht getan haben ... Wahrscheinlich etwas mit ObjC-Überbrückung zu tun? Melden Sie einen Fehler an (für Open Source ist Foundation Swift offen), wenn Sie eine bessere Lösung gefunden haben.
Rickster
2
Sie haben wahrscheinlich Recht, dass es mit Kleinbuchstaben beginnen sollte.
Hexdreamer
13

Einfacherer Weg:

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

Sie können NSNotification.Name einen benutzerdefinierten Initialisierer hinzufügen

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

Verwendung:

NotificationCenter.default.post(name: Notification.Name(.foo), object: nil)
efremidze
quelle
1
Kleinbuchstaben
@ Jalakoo Nur das cases in einer Aufzählung sollte in Kleinbuchstaben geschrieben werden, nicht die Aufzählung selbst. Typnamen werden in Großbuchstaben geschrieben und Aufzählungen sind Typen.
Manmal
9

Ich kann eine andere Option vorschlagen, die der von @CesarVarela vorgeschlagenen ähnelt.

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

Auf diese Weise können Sie Benachrichtigungen einfach veröffentlichen und abonnieren.

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

Hoffe das wird dir helfen.

Mikhail Glotov
quelle
4

Ich habe meine eigene Implementierung durchgeführt, um die Dinge von dort und dort zu mischen, und finde dies am bequemsten. Teilen für wen auch immer das interessiert sein könnte:

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
quelle
3
NSNotification.Name(rawValue: "myNotificationName")
Lee Probert
quelle
2

Dies ist nur eine Referenz

// 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)
user6943269
quelle
1

Der Vorteil der Verwendung von Aufzählungen besteht darin, dass der Compiler überprüft, ob der Name korrekt ist. Reduziert potenzielle Probleme und erleichtert das Refactoring.

Für diejenigen, die Enums anstelle von Anführungszeichen für Benachrichtigungsnamen verwenden möchten, ist dieser Code genau das Richtige:

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

Dann können Sie es so verwenden:

NotificationCenter.default.post(.somethingHappened)

Obwohl dies nichts mit der Frage zu tun hat, kann dies auch mit Storyboard-Abschnitten geschehen, um die Eingabe von Zeichenfolgen in Anführungszeichen zu vermeiden:

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

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

Nennen Sie es dann auf Ihrem View Controller wie folgt:

perform(segue: .unwindToX)
Eneko Alonso
quelle
> NotificationCenter.default.post(.somethingHappened)Dies löst einen Fehler aus. Die Methoden, die Sie in Ihrer Erweiterung hinzugefügt haben, akzeptieren mehr Argumente.
0

Wenn Sie benutzerdefinierte Benachrichtigungen nur für Zeichenfolgen verwenden, gibt es keinen Grund, Klassen zu erweitern String

    extension String {
        var notificationName : Notification.Name{
            return Notification.Name.init(self)
        }
    }
Quang Vĩnh Hà
quelle
0

@ CesarVarelas Antwort ist gut, aber um den Code etwas sauberer zu machen, können Sie Folgendes tun:

extension Notification.Name {
    typealias Name = Notification.Name

    static let onSelectedSkin = Name("on-selected-skin")
    static let onFoo = Name("on-foo")
}
ThomasW
quelle
0

Wenn Sie möchten, dass dies in einem Projekt, das gleichzeitig Objective-C und Swift verwendet, sauber funktioniert, war es für mich einfacher, die Benachrichtigungen in Objective-C zu erstellen.

Erstellen Sie eine .m / .h-Datei:

//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 Ihrem MyProject-Bridging-Header.h(nach Ihrem Projekt benannt), um sie Swift auszusetzen.

#import "CustomNotifications.h"

Verwenden Sie Ihre Benachrichtigungen in Objective-C wie folgt:

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

Und in Swift (5) so:

NotificationCenter.default.addObserver(self, selector: #selector(yourMethod(sender:)), name: .yourNotificationName, object: nil)
nickdnk
quelle