Jak tworzyć niestandardowe powiadomienia w Swift 3?

Odpowiedzi:

32

Możesz również użyć do tego protokołu

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

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

A następnie zdefiniuj nazwy powiadomień w enumdowolnym miejscu. Na przykład:

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

I używaj go jak

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

W ten sposób nazwy powiadomień zostaną oddzielone od Fundacji Notification.Name. I będziesz musiał zmodyfikować swój protokół tylko w przypadku implementacji Notification.Namezmian.

halil_g
źródło
Dokładnie tak, jak pierwotnie myślałem, że powinno działać - powiadomienia powinny być wyliczeniami. Dzięki za sztuczkę!
hexdreamer
Nie ma problemu! I edycja kodu zawierać konformacji przedłużenia do NotificationNametak namenieruchomość jest dodawany jedynie teksty stałe, które są zgodne z protokołem.
halil_g
Ściśle równoważne, ale bardziej logiczne IMO, możesz zdefiniować rozszerzenie w NotificationName (zamiast RawRepresentable) w następujący sposób:extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj
388

Jest na to czystszy (myślę) sposób

extension Notification.Name {

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

A potem możesz go używać w ten sposób

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
Cesar Varela
źródło
2
Używam powyższego kodu. To jest właściwość statyczna.
Cesar Varela
3
Bardzo czysty, bardzo mi się podoba
Tom Wolters
10
extension NSNotification.Name zamiast extension Notification.Name . W przeciwnym razie Swift 3 'Notification' is ambiguous for type lookup in this context
reklamuje
9
Dostajesz mój głos za wpisanie literówki w ciągu i tym samym zademonstrowanie wartości wpisanych nazw powiadomień: P
Dorian Roy,
10
Warto zauważyć, że jest to metoda zasugerowana przez Apple w sesji WWDC 2016 Session 207 developer.apple.com/videos/play/wwdc2016/207
Leon
36

Notification.post definiuje się jako:

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

W Objective-C nazwą powiadomienia jest zwykły NSString. W Swift jest zdefiniowany jako NSNotification.Name.

NSNotification.Name jest zdefiniowane jako:

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

To trochę dziwne, ponieważ spodziewałbym się, że będzie to Enum, a nie jakaś niestandardowa struktura, która pozornie nie ma więcej korzyści.

W Notification for NSNotification.Name występuje alias typu:

public typealias Name = NSNotification.Name

Mylące jest to, że zarówno Notification, jak i NSNotification istnieją w Swift

Aby zdefiniować własne niestandardowe powiadomienie, zrób coś takiego:

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

Następnie nazwij to:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
hexdreamer
źródło
3
Dobra odpowiedź. Kilka komentarzy: To trochę dziwne, ponieważ spodziewałbym się, że będzie to Enum - wyliczenie to zamknięty zestaw. Gdyby Notification.Namebyło wyliczeniem, nikt nie byłby w stanie zdefiniować nowych powiadomień. Używamy struktur dla innych typów wyliczeniowych, które muszą umożliwiać dodawanie nowych członków. (Zobacz propozycję szybkiej ewolucji .)
rickster
2
Mylące jest to, że zarówno Notification, jak i NSNotification istnieją w Swift - Notificationjest typem wartości (strukturą), dzięki czemu może skorzystać z semantyki języka Swift dla zmienności wartości (nie). Generalnie typy Foundation porzucają swój „NS” w Swift 3, ale tam, gdzie istnieje jeden z nowych Foundation Value Types, który go zastępuje, stary typ referencyjny pozostaje (zachowując nazwę „NS”), dzięki czemu można go nadal używać, gdy potrzebujesz semantyki referencji lub podklasy jej. Zobacz propozycję .
rickster
Pozwól, że wyjaśnię: oczekuję, że nazwy powiadomień będą wyliczeniami, tak jak błędy. Możesz zdefiniować własne wyliczenia błędów i dostosować je do ErrorType.
hexdreamer
1
To prawda - Apple mógł przynajmniej teoretycznie uczynić NotoficationName (lub jakiś taki) protokół, do którego tworzysz zgodne typy. Nie wiem, ale prawdopodobnie jest powód, dla którego tego nie zrobili… Prawdopodobnie ma to coś wspólnego z mostkami ObjC? Zgłoś błąd (w przypadku open source , Foundation Swift jest otwarta), jeśli masz wypracowane lepsze rozwiązanie.
rickster
2
Prawdopodobnie masz rację, ponieważ powinno zaczynać się od małych liter.
hexdreamer,
13

Łatwiejszy sposób:

let name:NSNotification.Name = NSNotification.Name("notificationName")
NotificationCenter.default.post(name: name, object: nil)
Zoltan Varadi
źródło
11

Możesz dodać niestandardowy inicjator do NSNotification.Name

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

Stosowanie:

NotificationCenter.default.post(name: Notification.Name(.foo), object: nil)
efremidze
źródło
1
Małe litery „typ wyliczenia” i „init (_ type: type)” dla
języka
@Jalakoo Tylko cases w wyliczeniu powinny być pisane małymi literami, a nie samo wyliczenie. Nazwy typów są pisane wielkimi literami, a wyliczenia są typami.
dzień
9

Mogę zasugerować inną opcję, podobną do tej, którą zasugerował @CesarVarela.

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

Umożliwi to łatwe publikowanie i subskrybowanie powiadomień.

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

Mam nadzieję, że to ci pomoże.

Michaił Głotow
źródło
4

Zrobiłem własną implementację, mieszając rzeczy stamtąd i tam i uważam to za najwygodniejsze. Udostępnianie dla kogo może być zainteresowany:

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
źródło
3
NSNotification.Name(rawValue: "myNotificationName")
Lee Probert
źródło
2

To tylko odniesienie

// 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
źródło
1

Zaletą używania wyliczeń jest to, że otrzymujemy kompilator, aby sprawdzić, czy nazwa jest poprawna. Zmniejsza potencjalne problemy i ułatwia refaktoryzację.

Dla tych, którzy lubią używać wyliczeń zamiast cytowanych ciągów znaków w nazwach powiadomień, ten kod załatwia sprawę:

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

Następnie możesz go użyć w ten sposób:

NotificationCenter.default.post(.somethingHappened)

Chociaż nie ma to związku z pytaniem, to samo można zrobić z fragmentami scenorysu, aby uniknąć wpisywania cytowanych ciągów:

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

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

Następnie na kontrolerze widoku nazwij to tak:

perform(segue: .unwindToX)
Eneko Alonso
źródło
> NotificationCenter.default.post(.somethingHappened)Powoduje to błąd; metody dodane w rozszerzeniu akceptują więcej argumentów.
0

jeśli używasz powiadomień niestandardowych zawierających tylko ciągi znaków, nie ma powodu, aby rozszerzać jakiekolwiek klasy, ale String

    extension String {
        var notificationName : Notification.Name{
            return Notification.Name.init(self)
        }
    }
Quang Vĩnh Ha
źródło
0

Odpowiedź @ CesarVarela jest dobra, ale aby uczynić kod nieco czystszym, możesz wykonać następujące czynności:

extension Notification.Name {
    typealias Name = Notification.Name

    static let onSelectedSkin = Name("on-selected-skin")
    static let onFoo = Name("on-foo")
}
Thomas W.
źródło
0

Jeśli chcesz, aby to działało czysto w projekcie, który używa jednocześnie Objective-C i Swift, stwierdziłem, że łatwiej jest tworzyć powiadomienia w Objective-C.

Utwórz plik .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";

W swoim MyProject-Bridging-Header.h(nazwanym na cześć twojego projektu), aby pokazać je Swift.

#import "CustomNotifications.h"

Korzystaj z powiadomień w Objective-C w ten sposób:

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

A w Swift (5) w ten sposób:

NotificationCenter.default.addObserver(self, selector: #selector(yourMethod(sender:)), name: .yourNotificationName, object: nil)
nickdnk
źródło