The NotificationCenter is a very useful tool. But using the API in Foundation is verbose and driven entirely by strings and values typed Any
. Using these values every time a notification is needed is just awkward.
Using generics in Swift, we can make this much nicer, so we can focus on using the contents of a strongly typed notification.
Install via CocoaPods
pod 'SweetNotifications'
UIKeyboard notifications are incorporated under the subspec SweetNotifications/UIKeyboard
. The 4 types of notifications are:
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
Take UIKeyboardWillShowNotification
as an example, to register:
listener = NotificationCenter.default.watch { (notification: UIKeyboardWillShowNotification) in
print(notification.frameBegin)
print(notification.frameEnd)
print(notification.animationDuration)
print(notification.animationOption)
print(notification.isCurrentApp)
}
Don't forget to remove the observer later, (or see automatic dereigstrations):
NotificationCenter.default.removeObserver(listener)
There are notifications that don't contain a userInfo
dictionary. In those situations we can simply watch for the named events:
listener = NotificationCenter.default.watch(for: Notification.Name.UIApplicationWillTerminate) {
// save all the important things!
}
Remembering to remove observers can be a little annoying, not to mention mundane. Use ObserverContainer
to automatically deregister all observers on deinit:
// declare a property
private let observerContainer = ObserverContainer()
func initalize() {
let listener1 = NotificationCenter.default.watch(for: Notification.Name.dataArrived) { ... }
let listener2 = NotificationCenter.default.watch(for: Notification.Name.loggedOut) { ... }
observerContainer.add(listener1)
observerContainer.add(listener2)
}
// no need to write your own `deinit` to remove all listeners!
It's easy to write your own notification types by adopting SweetNotification
:
struct ValueChangedNotification: SweetNotification {
let source: Source
init(userInfo: [AnyHashable: Any]) throws {
guard let sourceString = userInfo["source"] as? String else {
throw SerializationError.missingSource
}
switch userInfo["source"] as? String {
case "API"?:
source = .api
case "local"?:
source = .local
default:
throw SerializationError.unknownSource(userInfo["source"] as? String ?? "<Not a string>")
}
}
init(source: Source) {
self.source = source
}
func toUserInfo() -> [AnyHashable: Any]? {
switch source {
case .api:
return ["source": "API"]
case .local
return ["source": "local"]
}
}
enum Source {
case api, local
}
}
It's all type safe again :)
let valueChangedNotification = ValueChangedNotification(source: .api)
NotificationCenter.default.post(valueChangedNotification)
Testing custom notifications is very straight forward:
func testInitWithUserInfo_sourceStringIsValid_sourceSetCorrectly() {
let expectedPairs: [String: ValueChangedNotification.Source] = ["API": .api, "local": .local]
for (sourceString, expectedSource) in expectedPairs {
let userInfo: [String: String] = ["source": sourceString]
do {
let notification = try ValueChangedNotification(userInfo: userInfo)
XCTAssertEqual(expectedSource, notification.source)
} catch {
XCTFail("Unexpected failure: \(error)")
}
}
}
- add global error handler
- add keyboard notifications types
- auto-deregistration of listeners
- cross platform support
- CI
- Carthage and SPM support(?)