Latest 1.2.1
Homepage https://github.com/FutureWorkshops/Notifiable-iOS
License Apache License Version 2.0
Platforms ios 8.0, requires ARC
Frameworks SystemConfiguration
Authors

Notifiable is a set of utility classes to easily integrate with
Notifiable-Rails.

It handles device token registration and takes care of retrying failed requests and avoiding duplicate registrations.

Registering existing token for different user will result in token being reassigned.

Setup

Project integration

The Notifiable for iOS is available on CocoaPods. To install using it, just add the line to your Podfile:

pod 'Notifiable'

If you are not using CocoaPods, you can clone this project and import the files into your own project. This libraries uses AFNetworking as a dependency and is configured as a submodule.

You can see an example of the implementation in the Sample folder.

Configuring the SDK

Before using the NotifiableManager instances, and methods, it is necessary to set the server that the SDK will be talking to, and the group id that will have access to that configuration.

NotifiableManager.configure(url: <<SERVER_URL>>, accessId: <<USER_API_ACCESS_ID>>, secretKey: <<USER_API_SECRET_KEY>>, groupId: <<GROUP_ID>>)

Group ID

If you have a notification extension, you may want to share the Notifiable SDK configuration between your app, and said extension. To do that, the SDK uses the concept of App Group.

Use

To use the NotifiableManager, create a new object passing your server URL, application access id, application secret key. You can, also, provide blocks that will be used to notify your code when the device is registered for remote notifications and when it receives a new notification.

self.manager = NotifiableManager(groupId: <<GROUP_ID>>, didRegisterBlock: { [unowned self] (manager, token) -> Void in 
    ...
}, andNotificationBlock: { [unowned self] (manager, device, notification) -> Void in
    ...
})

Forward application events

Forward device token to NotifiableManager:

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) 
{
    NotifiableManager.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}

Foward new notifications to NotifiableManager:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

    guard NotifiableManager.isValidNotification(userInfo) else {
        completionHandler(.noData)
        return
    }

    NotifiableManager.markAsReceived(notification: userInfo, groupId: kAppGroupId) { (error) in
        if let _ = error {
            completionHandler(.failed)
        } else {
            completionHandler(.newData)
        }
    }
}

Listen to application events

To be notified when the device is registered for remote notifications or received a remote notification, you can use the blocks in the NotifiableManager init or register an object as a NotifiableManagerListener

func viewDidLoad() {
    super.viewDidLoad()
    NotifiableManager.register(listener: self)
}

//MARK: NotifiableManagerListener methods
func applicationDidRegisterForRemoteNotification(token: NSData) {
    ...
}

func applicationDidReceive(notification: [NSObject : AnyObject]) {
    ...
}

func manager(_ manager: NotifiableManager, didRegisterDevice device: NotifiableDevice) {
    ...
}

func manager(_ manager: NotifiableManager, didFailToRegisterDevice error: NSError) {
    ...
}

Registering a device

After the application:didRegisterForRemoteNotificationsWithDeviceToken: you can use the device token to register this device in the Notifiable-Rails server.

You can register an anonymous device:

override func viewDidLoad() {
    super.viewDidLoad()

    //1 - Config manager
    self.manager = NotifiableManager(groupId: <<GROUP_ID>>, didRegisterBlock: { [unowned self] (manager, token) -> Void in
        //3 - Register device
        self.registerDevice(manager, token: token)
    }, andNotificationBlock: nil)

    //2 - Request for permission
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (authorized, error) in
        DispatchQueue.main.async {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }
}

func registerDevice(manager:NotifiableManager, token:NSData) {
    manager.register(name:"iPhone", locale: NSLocale.autoupdatingCurrentLocale(), properties: ["onsite":true]) { (device, error) -> Void in
        ...
    }
}

Or register a device associated to a user:

func registerDevice(manager:NotifiableManager, token:NSData) {
    manager.register(name:"device", userAlias: "user", locale: NSLocale.autoupdatingCurrentLocale(), properties: ["onsite":true]) { (device, error) -> Void in
        ...       
    }
}

The properties dictionary holds some extended parameters that represents the meta data about the device, here you could send the current latitude and longitude of the device, for example.

You can access the registered device informations in the currentDevice property of the manager:

let device = self.manager.currentDevice

Updating the device informations

Once that the device is registered, you can update the device informations:

self.manager.update(token: nil, name: "device", userAlias: "user", location: NSLocale.currentLocale(), properties: ["onsite":true]) { (device, error) -> Void in
    ...
}

You can, also, associate the device to other user:

self.manager.associated(to: user, completionHandler: { (device, error) -> Void in
    ...
}

Or anonymize the token:

self.manager.anonymise { (device, error) -> Void in
    ...
}

Unregister a device

You may wish to unregister a device token (on user logout or in-app opt out perhaps).

self.manager.unregister { (device, error) -> Void in
    ...
}

Notification validation

If you have multiple services/frameworks that can dispatch a notification to your app, you can check if the notification was sent by Notifiable before trying to make requests to the server.

NotifiableManager.isValidNotification(userInfo)

Update notification state

iOS has some specific rules to call your app when a notification is received, or open. To ensure that the Notifiable server is displaying the correct state for a notification, the app will have to use the SDK to inform the change of such state, since the methods called by the system are beyond the SDK reaches.

Marking a notification as opened

When the user taps on a notification, after iOS 10, there are two places were the system informs the app that this action was made:

  1. By receiving an UIApplicationLaunchOptionsKey.remoteNotification on the launchOptions of application(_:didFinishLaunchingWithOptions:)
  2. If the app is on foreground, and you have a configured UNUserNotificationCenter, the userNotificationCenter(_:didReceive:withCompletionHandler:) is called.

On both cases, you can use the method markAsOpen(notification:groupId:completion:) (where the groupId is optional) to inform the server that the user opened a notification.

Notification validation

If you have multiple services/frameworks that can dispatch a notification to your app, you can check if the notification was sent by Notifiable before trying to make requests to the server.

NotifiableManager.isValidNotification(userInfo)

Application Did Finish Launching

if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [NSObject:AnyObject], NotifiableManager.isValidNotification(remoteNotification) {
    NotifiableManager.markAsOpen(notification: remoteNotification, groupId: kAppGroupId, completion: nil)
}

UNUserNotificationCenter

When operating with the UNUserNotificationCenterDelegate, it is better to call the completionHandler after the NotifiableManager finishes its operation. This will ensure that the network request to the Notifiable server will be completed.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    let userInfo = response.notification.request.content.userInfo

    guard NotifiableManager.isValidNotification(userInfo) else {
        completionHandler()
        return
    }

    NotifiableManager.markAsOpen(notification: userInfo, groupId: kAppGroupId) { (_) in
        completionHandler()
    }
}

Marking a notification as received

The iOS system has many entry points to indicate that a remote notification was received by the device. To be able to have a consistent status of a notification, you need to mark a notification as received as soon as the system makes it possible. This can be done by using the method NotifiableManager.markAsReceived(notification:, groupId:, completion:) (where the groupId is optional) to update the notification state in the server.

Application Did Receive Remote Notification

If your server sends a message with content-available: 1, and your app has Remote notification enabled in the capabilities, the system may awake your app to notify about the notification that arrived. At this point, you can call the server to update that notification status.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

    guard NotifiableManager.isValidNotification(userInfo) else {
        completionHandler(.noData)
        return
    }

    NotifiableManager.markAsReceived(notification: userInfo, groupId: kAppGroupId) { (error) in
        if let _ = error {
            completionHandler(.failed)
        } else {
            completionHandler(.newData)
        }
    }
}

As stated on Apple’s documentation:

If you enabled the remote notifications background mode, the system launches your app (or wakes it from the suspended state) and puts it in the background state when a remote notification arrives. However, the system does not automatically launch your app if the user has force-quit it. In that situation, the user must relaunch your app or restart the device before the system attempts to launch your app automatically again.

UNUserNotificationCenter

If the app is on foreground, and you have a configured UNUserNotificationCenter, the userNotificationCenter(_:willPresent:withCompletionHandler:) will be called, and it is the best moment to update the notification state.

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

    let userInfo = notification.request.content.userInfo

    guard NotifiableManager.isValidNotification(userInfo) else {
        return
    }

    NotifiableManager.markAsReceived(notification: userInfo, groupId: kAppGroupId) { (error) in
        completionHandler(.alert)
    }
}

Notification Service Extension

If the server sends a notification with mutable-content: 1 APNS property, you may implement a notification service extension, and, there, update the status of the notification in the server.

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    NotifiableManager.markAsReceived(notification: request.content.userInfo, groupId: kAppGroupId) { [weak self] (_) in
        guard let contentHandler = self?.contentHandler, let bestAttempt = self?.bestAttemptContent else { return }
        contentHandler(bestAttempt)
    }
}

This extension will be called only when both of the following conditions are met:

  • The remote notification is configured to display an alert.
  • The remote notification’s aps dictionary includes the mutable-content key with the value set to 1.

LICENSE

Apache License Version 2.0

Latest podspec

{
    "name": "Notifiable",
    "version": "1.2.1",
    "platforms": {
        "ios": "8.0"
    },
    "summary": "Utility classes to integrate with Notifiable-Rails gem",
    "ios": {
        "frameworks": "MobileCoreServices"
    },
    "frameworks": "SystemConfiguration",
    "description": "Utility classes to integrate with Notifiable-Rails gem (https://github.com/FutureWorkshops/notifiable-rails).nYou can see a sample of how to use the FWTNotifiable SDK on github: (https://github.com/FutureWorkshops/Notifiable-iOS/tree/master/Sample)",
    "homepage": "https://github.com/FutureWorkshops/Notifiable-iOS",
    "license": {
        "type": "Apache License Version 2.0",
        "file": "LICENSE"
    },
    "authors": {
        "Future Workshops": "[email protected]"
    },
    "source": {
        "git": "https://github.com/FutureWorkshops/Notifiable-iOS.git",
        "tag": "1.2.1"
    },
    "source_files": "Notifiable-iOS/**/*.{h,m}",
    "public_header_files": [
        "Notifiable-iOS/FWTNotifiableManager.h",
        "Notifiable-iOS/Logger/FWTNotifiableLogger.h",
        "Notifiable-iOS/Model/FWTNotifiableDevice.h",
        "Notifiable-iOS/Category/*.h"
    ],
    "module_name": "FWTNotifiable",
    "requires_arc": true
}

Pin It on Pinterest

Share This