¿Cómo se crean notificaciones personalizadas en Swift 3?

Respuestas:

32

También puede utilizar un protocolo para esto

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

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

Y luego defina los nombres de sus notificaciones como un enumlugar que desee. Por ejemplo:

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

Y usarlo como

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

De esta manera, los nombres de las notificaciones se desvincularán de la Fundación Notification.Name. Y solo tendrás que modificar tu protocolo en caso de que la implementación Notification.Namecambie.

halil_g
fuente
Esta es exactamente la forma en que originalmente pensé que debería funcionar: las notificaciones deben ser enumeraciones. ¡Gracias por el truco!
Hexdreamer
¡No hay problema! Edité el código para incluir la conformación de la extensión para NotificationNameque la namepropiedad solo se agregue a las enumeraciones que se ajustan al protocolo.
halil_g
En mi opinión, estrictamente equivalente pero más lógico, puede definir la extensión en NotificationName (en lugar de RawRepresentable) así:extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj
387

Hay una forma más limpia (creo) de lograrlo

extension Notification.Name {

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

Y luego puedes usarlo así

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
Cesar Varela
fuente
2
Estoy usando el código de arriba. Ésta es una propiedad estática.
Cesar Varela
3
Muy limpio, me gusta mucho
Tom Wolters
10
extension NSNotification.Name en lugar de extension Notification.Name . De lo contrario, Swift 3 quejas con'Notification' is ambiguous for type lookup in this context
lluisgh
9
Obtienes mi voto a favor por cometer un error tipográfico en la cadena y así demostrar el valor de los nombres de notificación escritos: P
Dorian Roy
10
Vale la pena señalar que este es el método sugerido por Apple en la WWDC 2016 Session 207 developer.apple.com/videos/play/wwdc2016/207
Leon
36

Notification.post se define como:

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

En Objective-C, el nombre de la notificación es un NSString simple. En Swift, se define como NSNotification.Name.

NSNotification.Name se define como:

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

Esto es un poco extraño, ya que esperaría que fuera un Enum, y no una estructura personalizada sin aparentemente más beneficio.

Hay un typealias en Notification for NSNotification.Name:

public typealias Name = NSNotification.Name

La parte confusa es que tanto Notification como NSNotification existen en Swift

Entonces, para definir su propia notificación personalizada, haga algo como:

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

Entonces para llamarlo:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
Hexdreamer
fuente
3
Buena respuesta. Algunos comentarios: Esto es un poco extraño, ya que esperaría que fuera una enumeración: una enumeración es un conjunto cerrado . Si Notification.Namefuera una enumeración, nadie podría definir nuevas notificaciones. Usamos estructuras para tipos similares a enumeraciones que necesitan permitir agregar nuevos miembros. (Ver la propuesta de evolución rápida .)
rickster
2
La parte confusa es que tanto Notification como NSNotification existen en Swift : Notificationes un tipo de valor (una estructura), por lo que puede beneficiarse de la semántica de Swift para la (im) mutabilidad del valor. Por lo general, los tipos de base eliminan su "NS" en Swift 3, pero cuando existe uno de los nuevos tipos de valor de base para suplantarlo, el tipo de referencia anterior se mantiene (conservando el nombre "NS") para que pueda seguir utilizándolo cuando necesita semántica de referencia o subclasificarla. Vea la propuesta .
rickster
Permítanme aclarar: espero que los nombres de notificación sean enumeraciones, como lo son los Errores. Puede definir sus propias enumeraciones de Error y hacer que se ajusten a ErrorType.
hexdreamer
1
Es cierto: Apple podría, al menos teóricamente, haber hecho de NotoficationName (o algo así) un protocolo, al que se crean tipos conformes. No lo sé, pero es probable que haya una razón por la que no lo hicieron ... ¿Probablemente algo que ver con el puente de ObjC? Presenta un error (a código abierto , Foundation Swift está a la vista) si tienes una solución mejor.
rickster
2
Probablemente tenga razón en que debe comenzar con minúsculas.
hexdreamer
13

Manera más fácil:

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

Puede agregar un inicializador personalizado a NSNotification.Name

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

Uso:

NotificationCenter.default.post(name: Notification.Name(.foo), object: nil)
efremidze
fuente
1
Minúsculas 'enum type' e 'init (_ type: type)' para Swift 3.0.2
Jalakoo
@Jalakoo Solo las cases en una enumeración deben estar en minúsculas, no la enumeración en sí. Los nombres de los tipos están en mayúsculas y las enumeraciones son tipos.
manmal
9

Puedo sugerir otra opción similar a lo que sugirió @CesarVarela.

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

Esto te permitirá publicar y suscribirte a las notificaciones fácilmente.

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

Espero que esto te ayudará.

Mikhail Glotov
fuente
4

Hice mi propia implementación mezclando cosas de allí y de allí, y encontré esto como lo más conveniente. Compartiendo para quien pueda estar interesado:

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)
    }
}
Iñigo333
fuente
3
NSNotification.Name(rawValue: "myNotificationName")
Lee Probert
fuente
2

Esto es solo una referencia

// 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
fuente
1

La ventaja de utilizar enumeraciones es que conseguimos que el compilador compruebe que el nombre es correcto. Reduce problemas potenciales y facilita la refactorización.

Para aquellos a los que les gusta usar enumeraciones en lugar de cadenas entre comillas para los nombres de notificación, este código funciona:

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

Entonces puedes usarlo así:

NotificationCenter.default.post(.somethingHappened)

Aunque no está relacionado con la pregunta, se puede hacer lo mismo con las secuencias del guión gráfico, para evitar escribir cadenas entre comillas:

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

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

Luego, en su controlador de vista, llámelo como:

perform(segue: .unwindToX)
Eneko Alonso
fuente
> NotificationCenter.default.post(.somethingHappened)Esto arroja un error; los métodos que agregaste en tu extensión aceptan más argumentos.
0

si usa notificaciones personalizadas solo de cadena, no hay razón para extender ninguna clase, pero String

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

La respuesta de @ CesarVarela es buena, pero para hacer que el código sea un poco más limpio, puede hacer lo siguiente:

extension Notification.Name {
    typealias Name = Notification.Name

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

Si desea que esto funcione de manera limpia en un proyecto que usa Objective-C y Swift al mismo tiempo, descubrí que es más fácil crear las notificaciones en Objective-C.

Cree un archivo .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";

En su MyProject-Bridging-Header.h(nombre de su proyecto) para exponerlos a Swift.

#import "CustomNotifications.h"

Use sus notificaciones en Objective-C de esta manera:

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

Y en Swift (5) así:

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