Comment créer des notifications personnalisées dans Swift 3?

Réponses:

32

Vous pouvez également utiliser un protocole pour cela

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

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

Et puis définissez vos noms de notification comme enumvous le souhaitez. Par exemple:

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

Et utilisez-le comme

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

De cette façon, les noms des notifications seront découplés de la Fondation Notification.Name. Et vous n'aurez qu'à modifier votre protocole en cas de mise en œuvre pour des Notification.Namechangements.

halil_g
la source
C'est exactement la façon dont je pensais à l'origine que cela devrait fonctionner - les notifications devraient être des énumérations. Merci pour l'astuce!
hexdreamer
Aucun problème! J'ai édité le code pour inclure la conformation de l'extension afin NotificationNameque la namepropriété ne soit ajoutée qu'aux énumérations conformes au protocole.
halil_g
IMO strictement équivalent mais plus logique, vous pouvez définir l'extension sur NotificationName (au lieu de RawRepresentable) comme ceci:extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj
388

Il existe un moyen plus propre (je pense) d'y parvenir

extension Notification.Name {

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

Et puis tu peux l'utiliser comme ça

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
Cesar Varela
la source
2
J'utilise le code ci-dessus. C'est une propriété statique.
Cesar Varela
3
Très propre, je l'aime beaucoup
Tom Wolters
10
extension NSNotification.Name au lieu de extension Notification.Name . Sinon Swift 3 plaintes avec'Notification' is ambiguous for type lookup in this context
lluisgh
9
Vous obtenez mon vote positif pour avoir fait une faute de frappe dans la chaîne et ainsi démontrer la valeur des noms de notification tapés: P
Dorian Roy
10
Il pourrait être intéressant de noter que c'est la méthode suggérée par Apple dans WWDC 2016 Session 207 developer.apple.com/videos/play/wwdc2016/207
Leon
36

Notification.post est défini comme:

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

En Objective-C, le nom de la notification est une NSString simple. Dans Swift, il est défini comme NSNotification.Name.

NSNotification.Name est défini comme:

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

C'est un peu bizarre, car je m'attendrais à ce que ce soit un Enum, et non une structure personnalisée avec apparemment aucun avantage.

Il existe un typealias dans Notification for NSNotification.Name:

public typealias Name = NSNotification.Name

La partie déroutante est que la notification et la NSNotification existent dans Swift

Donc, pour définir votre propre notification personnalisée, faites quelque chose comme:

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

Alors pour l'appeler:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
rêveur
la source
3
Bonne réponse. Quelques commentaires: C'est un peu bizarre, car je m'attendrais à ce que ce soit un Enum - Un enum est un ensemble fermé . S'il Notification.Names'agissait d'une énumération, personne ne pourrait définir de nouvelles notifications. Nous utilisons des structures pour les types de type autrement enum qui doivent permettre l'ajout de nouveaux membres. (Voir la proposition d'évolution rapide .)
rickster
2
La partie déroutante est que Notification et NSNotification existent dans Swift - Notificationest un type valeur (une structure), de sorte qu'il puisse bénéficier de la sémantique de Swift pour la (im) mutabilité de la valeur. Généralement, les types Foundation abandonnent leur "NS" dans Swift 3, mais là où l'un des nouveaux types de valeur Foundation existe pour le remplacer, l'ancien type de référence reste (en conservant le nom "NS") afin que vous puissiez toujours l'utiliser lorsque vous avez besoin d'une sémantique de référence ou de la sous-classer. Voir la proposition .
rickster
Permettez-moi de clarifier: je m'attends à ce que les noms de notification soient des énumérations, comme le sont les erreurs. Vous pouvez définir vos propres énumérations Error et les rendre conformes à ErrorType.
hexdreamer
1
Vrai - Apple pourrait au moins théoriquement avoir fait de NotoficationName (ou d'un autre) un protocole, auquel vous créez des types conformes. Je ne sais pas, mais il y a probablement une raison pour laquelle ils ne l'ont pas fait ... Probablement quelque chose à voir avec le pontage ObjC? Déposez un bogue (en open source , Foundation Swift est à découvert) si vous avez une meilleure solution élaborée.
rickster
2
Vous avez probablement raison de dire qu'il doit commencer par des minuscules.
hexdreamer
13

Manière plus simple:

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

Vous pouvez ajouter un initialiseur personnalisé à 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
la source
1
Minuscules 'enum type' et 'init (_ type: type)' pour Swift 3.0.2
Jalakoo
@Jalakoo Seuls les cases dans une énumération doivent être en minuscules, pas l'énumération elle-même. Les noms de type sont en majuscules et les énumérations sont des types.
manmal
9

Je peux suggérer une autre option qui est similaire à ce que @CesarVarela a suggéré.

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

Cela vous permettra de publier et de vous abonner facilement aux notifications.

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

J'espère que ceci vous aidera.

Mikhail Glotov
la source
4

J'ai fait ma propre implémentation en mélangeant les choses à partir de là et de là, et je trouve que c'est la plus pratique. Partage pour ceux qui pourraient être intéressés:

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

C'est juste une référence

// 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
la source
1

L'avantage d'utiliser des enums est que nous demandons au compilateur de vérifier que le nom est correct. Réduit les problèmes potentiels et facilite la refactorisation.

Pour ceux qui aiment utiliser des énumérations au lieu de chaînes entre guillemets pour les noms de notification, ce code fait l'affaire:

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

Ensuite, vous pouvez l'utiliser comme ceci:

NotificationCenter.default.post(.somethingHappened)

Bien que sans rapport avec la question, la même chose peut être faite avec les séquences de storyboard, pour éviter de taper des chaînes entre guillemets:

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

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

Ensuite, sur votre contrôleur de vue, appelez-le comme:

perform(segue: .unwindToX)
Eneko Alonso
la source
> NotificationCenter.default.post(.somethingHappened)Cela génère une erreur; les méthodes que vous avez ajoutées dans votre extension acceptent plus d'arguments.
0

si vous utilisez des notifications personnalisées contenant uniquement des chaînes, il n'y a aucune raison d'étendre les classes mais String

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

La réponse de @ CesarVarela est bonne, mais pour rendre le code légèrement plus propre, vous pouvez faire ce qui suit:

extension Notification.Name {
    typealias Name = Notification.Name

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

Si vous souhaitez que cela fonctionne correctement dans un projet qui utilise à la fois Objective-C et Swift en même temps, j'ai trouvé qu'il était plus facile de créer les notifications dans Objective-C.

Créez un fichier .m / .h:

//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";

Dans votre MyProject-Bridging-Header.h(nommé d'après votre projet) pour les exposer à Swift.

#import "CustomNotifications.h"

Utilisez vos notifications dans Objective-C comme ceci:

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

Et dans Swift (5) comme ceci:

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