Skip to content

allen-zeng/SweetNotifications

Repository files navigation

SweetNotifications

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.

Quick Start

Install via CocoaPods

pod 'SweetNotifications'

UIKeyboard notifications

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)

Listening for named events only

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

Automatic deregistration

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!

Custom notification types

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

Posting custom notifications

It's all type safe again :)

let valueChangedNotification = ValueChangedNotification(source: .api)
NotificationCenter.default.post(valueChangedNotification)

Unit testing

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

Roadmap

  • add global error handler
  • add keyboard notifications types
  • auto-deregistration of listeners
  • cross platform support
  • CI
  • Carthage and SPM support(?)