SDK

Remote Notifications

In this page you'll learn how notifications are handled in your app and what all the options at your disposal to create a great messaging experience for your users.

Notificare library accepts several types of interactive and actionable notifications that will be handled for you without any extra development. If you are going to prevent this default behaviour, please note that you will have to either handle all the functionality yourself (metrics logging, presenting UI or collect replies) or if you don't, you understand that some features will not work as advertised.

Receiving Notifications

The first thing you need to handle all types of notifications, is to make sure your app is declaring the following permissions in your app's .plist file:

ios camera privacy plist

This will make sure we request access to the device's camera or photo library whenever it is needed.

Whenever a remote notification arrives and a user clicks in a notification in the lock screen or notification center, if your app is inactive, the only way to handle it is from the launching options. To make sure you handle this correctly, you must implement the handleOptions method, in your AppDelegate.m or AppDelegate.swift as follows:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ...more code

    [[NotificarePushLib shared] handleOptions:launchOptions];

    ...more code
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

    ...more code

    if launchOptions != nil {
        NotificarePushLib.shared().handleOptions(launchOptions!)
    }

    ...more code
}

If your app is in background or foreground (pre iOS 10) or only background (iOS 10 and higher), when a notification arrives, iOS will trigger the following application delegate. Implement the following to make sure you handle this correctly:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{

    [[NotificarePushLib shared] handleNotification:userInfo forApplication:application completionHandler:^(NSDictionary *info) {

        completionHandler(UIBackgroundFetchResultNewData);

    } errorHandler:^(NSError *error) {

        completionHandler(UIBackgroundFetchResultNoData);

    }];
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    NotificarePushLib.shared().handleNotification(userInfo, for: application, completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
        completionHandler(.newData)
    }, errorHandler: {(_ error: Error?) -> Void in
        completionHandler(.noData)
    })
}

The method above will make sure your app can handle silent notifications and that it can download content in response to a push notification. In iOS 9 or below this method will also handle notifications clicked from lock screen or the notification center.

Additionally in iOS 10 and higher you will need to also implement the following method to handle correctly notifications received in the foreground:

- (void)notificarePushLib:(NotificarePushLib *)library willHandleNotification:(UNNotification *)notification{

    [[NotificarePushLib shared] handleNotification:notification completionHandler:^(NSDictionary *info) {

    } errorHandler:^(NSError *error) {

    }];

}
func notificarePushLib(_ library: NotificarePushLib, willHandle notification: UNNotification) {
    NotificarePushLib.shared().handle(notification, completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
    }, errorHandler: {(_ error: Error?) -> Void in
    })
}

Please note that none of the methods above will show any content. In most cases when your app is being used you will not want to interrupt the user by presenting content from an incoming notification. It is a good practice to instead use our inbox mechanisms to simply show a badge in your app and let the user see the unread messages from the inbox list. But if your use case requires you to actually present content whenever a notification is received independently if the user is using your app or not, you can change that behaviour as shown below:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{

    [[NotificarePushLib shared] openNotification:userInfo];

}

- (void)notificarePushLib:(NotificarePushLib *)library willHandleNotification:(UNNotification *)notification{

    [[NotificarePushLib shared] openNotification:[[[[notification request] content] userInfo]];

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

    NotificarePushLib.shared().openNotification(userInfo)

}

func notificarePushLib(_ library: NotificarePushLib, willHandle notification: UNNotification) {
    NotificarePushLib.shared().openNotification(notification.request.content.userInfo)
}

Optionally in iOS 10 or higher, you can change the presentation options when your app is in the foreground. By default our library will set this to UNNotificationPresentationOptionNone, but you can change this behaviour to match your needs:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ...more code

    [[NotificarePushLib shared] setNotificationPresentationOptions:UNNotificationPresentationOptionAlert];

    ...more code
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

    ...more code

    NotificarePushLib.shared().notificationPresentationOptions = UNNotificationPresentationOptionAlert

    ...more code
}

Handling Actionable Notifications

Remote notifications in iOS can include actions that users can click from the lock screen or notification center. In iOS 10 and higher this interaction is automatically handled for you without any extra implementation, but if you are supporting older versions you will need additional methods to handle this correctly.

For iOS 9 please implement the following delegate method in your AppDelegate.m or AppDelegate.swift:

- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forRemoteNotification:(nonnull NSDictionary *)userInfo withResponseInfo:(nonnull NSDictionary *)responseInfo completionHandler:(nonnull void (^)())completionHandler{

    [[NotificarePushLib shared] handleAction:identifier forNotification:userInfo withData:responseInfo completionHandler:^(NSDictionary *info) {

        completionHandler();

    } errorHandler:^(NSError *error) {

        completionHandler();

    }];
}
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
    NotificarePushLib.shared().handleAction(identifier, forNotification: userInfo, withData: responseInfo, completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
        completionHandler()
    }, errorHandler: {(_ error: Error?) -> Void in
        completionHandler()
    })
}

For iOS 8 please implement the following delegate method in your AppDelegate.m or AppDelegate.swift:

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completion{

    [[NotificarePushLib shared] handleAction:identifier forNotification:userInfo withData:nil completionHandler:^(NSDictionary *info) {
        completion();
    } errorHandler:^(NSError *error) {
        completion();
    }];

}
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
    NotificarePushLib.shared().handleAction(identifier, forNotification: userInfo, withData: nil, completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
        completion()
    }, errorHandler: {(_ error: Error?) -> Void in
        completion()
    })
}

Deep Links

In modern apps, this is a great way of creating interactions between notifications and your own app content, allowing you to send messages that can eventually drive the user to open content hidden deeper in your app.

To prepare your app to handle deep links is not complicated and will allow you to handle not only Deep Link notification types, but also any link made from an web page. In order to indicate your app that you will handle a custom URL scheme you simply have to declare the following in your app's .plist file:

ios url schemes

Because these deep links can also be called from actions in your notifications, you must also declare it the Launch Services Queries Schemes in your app's .plist file as shown below:

ios launch services url schemes

Then in your AppDelegate.m or AppDelegate.swift make sure your implement the following methods:

//For iOS 9 and lower
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
    [[NotificarePushLib shared]  handleOpenURL:url];

    //Handle your deep linking

    return YES;
}


//For iOS 10 and higher
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{
    [[NotificarePushLib shared]  handleOpenURL:url];

    //Handle your deep linking
    return YES;
}
//For iOS 9 and lower
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    NotificarePushLib.shared().handleOpen(url)

    //Handle your deep linking

    return true
}

//For iOS 10 and higher
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    NotificarePushLib.shared().handleOpen(url)

    //Handle your deep linking
    return true
}

Another situation where you will also want to handle deep links is when users click in a link in a HTML or Web Page notification. If that link should open a view in your app you will want to intercept those clicks and handle it the same way you handle deep links.

First make sure you declare those URL Schemes in the Notificare.plist file as follows:

ios notificare plist url schemes

Our library will then intercept all the links using those URL Schemes and trigger the following delegate method:

- (void)notificarePushLib:(NotificarePushLib *)library didClickURL:(NSURL *)url inNotification:(NotificareNotification *)notification{
    //Handle Deep Link
}
func notificarePushLib(_ library: NotificarePushLib, didClick url: URL, in notification: NotificareNotification) {
    //Handle Deep Link
}

Inbox

With our library it's extremely easy to implement an in-app inbox. Implementing an inbox increases considerably the engagement rates of your notifications simply because messages will always be available inside your app. To activate the inbox functionality, please follow the instructions described here.

After activating this functionality you can start implementing your inbox in your app. To get all the items in the inbox you would basically invoke the following:

[[NotificarePushLib shared] fetchInbox:nil skip:nil limit:nil completionHandler:^(NSDictionary *info) {

    //By default we will give the last 25 messages
    //You can use skip and limit to change this default and create a paginator
    //Max. number of messages you can retrieve per page is 100
    NSArray * messages = [info objectForKey:@"inbox"];
    //This will be a list of NotificareDeviceInbox objects

    //Get the total number of messages in the inbox
    int total = [[info objectForKey:@"total"] intValue];

    //Get the total number of unread messages in the inbox
    int unread = [[info objectForKey:@"unread"] intValue];

} errorHandler:^(NSError *error) {
    //Handle error
}];
NotificarePushLib.shared().fetchInbox(nil, skip: nil, limit: nil, completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
    //By default we will give the last 25 messages
    //You can use skip and limit to change this default and create a paginator
    //Max. number of messages you can retrieve per page is 100
    var messages = info["inbox"] as? [Any] ?? [Any]()
    //This will be a list of NotificareDeviceInbox objects
    //Get the total number of messages in the inbox
    var total = CInt(info["total"] as? Int ?? 0)
    //Get the total number of unread messages in the inbox
    var unread = CInt(info["unread"] as? Int ?? 0)
}, errorHandler: {(_ error: Error?) -> Void in
    //Handle error
})

To open a message from the list of messages in the inbox, please use the following method:

[[NotificarePushLib shared] openInboxItem:(NotificareDeviceInbox *)item];
NotificarePushLib.shared().openInboxItem((item as? NotificareDeviceInbox))

At anytime you can also delete a notification from the inbox:

[[NotificarePushLib shared] removeFromInbox:(NotificareDeviceInbox *)item completionHandler:^(NSDictionary *info) {

    //Reload your inbox

} errorHandler:^(NSError *error) {

    //Handle error
}];
NotificarePushLib.shared().remove(fromInbox: (item as? NotificareDeviceInbox), completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
    //Reload your inbox
}, errorHandler: {(_ error: Error?) -> Void in
    //Handle error
})

Additionally you can also mark a message as read by invoking the following method:

[[NotificarePushLib shared] markAsRead:(NotificareDeviceInbox *)item completionHandler:^(NSDictionary *info) {

    //Reload your inbox

} errorHandler:^(NSError *error) {

    //Handle error
}];
NotificarePushLib.shared().mark(asRead: (item as? NotificareDeviceInbox), completionHandler: {(_ info: [AnyHashable: Any]) -> Void in
    //Reload your inbox
}, errorHandler: {(_ error: Error?) -> Void in
    //Handle error
})

Finally you can also delete all items in the inbox by invoking the following method:

[[[NotificarePushLib shared] clearInbox:^(NSDictionary *info) {

    //Reload your inbox

} errorHandler:^(NSError *error) {

    //Handle error
}];
NotificarePushLib.shared().clearInbox({(_ info: [AnyHashable: Any]) -> Void in
    //Reload your inbox
}, errorHandler: {(_ error: Error?) -> Void in
    //Handle error
})

If you've configured your inbox functionality to use the AutoBadge feature, then implement the following delegate in your AppDelegate.m or AppDelegate.swift:

- (void)notificarePushLib:(NotificarePushLib *)library didUpdateBadge:(int)badge{
    //Update UI or reload Inbox
}
func notificarePushLib(_ library: NotificarePushLib, didUpdateBadge badge: Int) {
    //Update UI or reload Inbox
}

If you using AutoBadge, you can also access your badge count of unread messages at any time by doing the following:

int badge = [[NotificarePushLib shared] myBadge];
var badge: Int = NotificarePushLib.shared().myBadge()

Notification Delegates

Optionally, you can implement delegate methods which can inform your app when certain operations are done or failed, so you can show more UX/UI elements, perform other operations, etc. In your AppDelegate.m or AppDelegate.swift you can add the following delegate methods:

- (void)notificarePushLib:(NotificarePushLib *)library willOpenNotification:(NotificareNotification *)notification{
    //Triggered whenever notification is about to be opened
}

- (void)notificarePushLib:(NotificarePushLib *)library didOpenNotification:(NotificareNotification *)notification{
    //Triggered when the notification does open
}

- (void)notificarePushLib:(NotificarePushLib *)library didClickURL:(NSURL *)url inNotification:(NotificareNotification *)notification{
    //Triggered whenever user clicks a link with a url scheme in the URL_SCHEMES array under OPTIONS in Notificare.plist
    //This only applies for notifications of type HTML or Web Page
}

- (void)notificarePushLib:(NotificarePushLib *)library didCloseNotification:(NotificareNotification *)notification{
    //Triggered when the user closes the notification or completes an action
}

- (void)notificarePushLib:(NotificarePushLib *)library didFailToOpenNotification:(NotificareNotification *)notification{
    //Triggered when notification failed to open
}
func notificarePushLib(_ library: NotificarePushLib, willOpen notification: NotificareNotification) {
    //Triggered whenever notification is about to be opened
}

func notificarePushLib(_ library: NotificarePushLib, didOpen notification: NotificareNotification) {
    //Triggered when the notification does open
}

func notificarePushLib(_ library: NotificarePushLib, didClick url: URL, in notification: NotificareNotification) {
    //Triggered whenever user clicks a link with a url scheme in the URL_SCHEMES array under OPTIONS in Notificare.plist
    //This only applies for notifications of type HTML or Web Page
}

func notificarePushLib(_ library: NotificarePushLib, didClose notification: NotificareNotification) {
    //Triggered when the user closes the notification or completes an action
}

func notificarePushLib(_ library: NotificarePushLib, didFailToOpen notification: NotificareNotification) {
    //Triggered when notification failed to open
}

Actions Delegates

The same can be achieved for actions. In your AppDelegate.m or AppDelegate.swift you can add the following delegate methods:

- (void)notificarePushLib:(NotificarePushLib *)library willExecuteAction:(NotificareNotification *)notification{
    //Triggered just before the action is going to be executed
}

- (void)notificarePushLib:(NotificarePushLib *)library didExecuteAction:(NSDictionary *)info{
    //Triggered when the action is executed
}

-(void)notificarePushLib:(NotificarePushLib *)library shouldPerformSelectorWithURL:(NSURL *)url{
    //Required if you are going to use actions of type "Call a method in your app"
    //Handle it pretty much the same way you would handle a deep link
}

- (void)notificarePushLib:(NotificarePushLib *)library didNotExecuteAction:(NSDictionary *)info{
    //Triggered when the action sheet is cancelled
}

- (void)notificarePushLib:(NotificarePushLib *)library didFailToExecuteAction:(NSError *)error{
    //Triggered when an action failed to be executed
}
func notificarePushLib(_ library: NotificarePushLib, willExecuteAction notification: NotificareNotification) {
    //Triggered just before the action is going to be executed
}

func notificarePushLib(_ library: NotificarePushLib, didExecuteAction info: [AnyHashable: Any]) {
    //Triggered when the action is executed
}

func notificarePushLib(_ library: NotificarePushLib, shouldPerformSelectorWith url: URL) {
    //Required if you are going to use actions of type "Call a method in your app"
    //Handle it pretty much the same way you would handle a deep link
}

func notificarePushLib(_ library: NotificarePushLib, didNotExecuteAction info: [AnyHashable: Any]) {
    //Triggered when the action sheet is cancelled
}

func notificarePushLib(_ library: NotificarePushLib, didFailToExecuteAction error: Error?) {
    //Triggered when an action failed to be executed
}

Notification Service Extension

In iOS 10, Apple introduced a new extension that enables your apps to process notifications before they are displayed to the users. This can be used to provide users with rich content in the lock screen or simply change the content of your messages in the client side of things.

To make use of this new feature you need to add a new target to your application:

xcode service extensions new target

This will prompt you to the extensions window where you should pick the Notification Service Extension:

ios service extension select extension

To complete adding the new target you must provide a product name and click finish:

ios service extension create target

This is going to add a new folder to your project with the new extension files.

Then if you are using Cocoapods, add the following to your Podfile:

target 'notification' do // use the name of the target you created previously
    pod 'notificare-push-lib', '~> 1.15' // make sure you use the same version as your main target
end

After that make sure you run (in the terminal):

pod install

You can proceed to implement your Notification Service Extension.

If you are not using Cocoapods, before you can start implementing your new extension you must also include all the necessary frameworks in this new target.

This new target will be work as a separated embed app inside your app. You need to add the libNotificarePushLib.a file and all its frameworks in order to add implement this extension correctly. Go ahead and open this new target's Build Phases tab:

ios service extension build phases

Expand the Link Binary with Libraries section and click in the plus button to start adding the necessary frameworks:

xcode add frameworks

One by one add the following frameworks to your project:

  • CoreNFC.framework (marked as optional)
  • AVFoundation.framework
  • CoreLocation.framework
  • CoreData.framework
  • UserNotifications.framework (marked as optional)
  • MobileCoreServices.framework
  • MessageUI.framework
  • libicucore.dylib
  • UIKit.framework
  • Foundation.framework
  • CoreGraphics.framework
  • PassKit.framework (marked as optional)
  • MapKit.framework
  • SystemConfiguration.framework
  • Security.framework
  • CFNetwork.framework
  • ImageIO.framework
  • StoreKit.framework
  • SafariServices.framework

If you choose to create your extension in Swift, you will need to add a bridging header to this target. In Xcode you do this by simply adding a new Cocoa Touch class (in Object-C) like shown below:

xcode add cocoa in swift

This will trigger a Xcode dialog like the one below:

xcode bridging header

By clicking Yes, Xcode will create a new file with a called TargetName-Bridging-Header.h, where TargetName corresponds to your own app’s target. You can now delete the Cocoa Touch file you've created.

Finally, in your TargetName-Bridging-Header.h add the following line:

#import "NotificarePushLib.h"
Please note that this extension is a different target and has its own Info.plist. This means that after iOS 9, Apple will not load non-secure content by default. To make it possible for apps to load content served over HTTP you will need to add a new entry in the extension Info.plist. If you're only going to our dashboard to create lock screen images then this step is not necessary.

If you are using our API to send lock screen media images that are hosted in another location and served over a non-secure protocol (http://) then declare the following in your extension's Info.plist:

declare ats in plist

After you've added the libNotificarePushLib.a and all these frameworks your Link Binary with Libraries will look like this:

xcode added frameworks

After this you are ready to start implementing the extension. In your newly created implementation file, in the didReceiveNotificationRequest method, you must add the following in order to display whatever you end as the lock screen media of your messages:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];


    [[NotificarePushLib shared] fetchAttachment:request.content.userInfo completionHandler:^(NSArray *info) {
        self.bestAttemptContent.attachments = info;
        self.contentHandler(self.bestAttemptContent);
    } errorHandler:^(NSError *error) {
        self.contentHandler(self.bestAttemptContent);
    }];

}
func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = request.content
    NotificarePushLib.shared().fetchAttachment(request.content.userInfo, completionHandler: {(_ info: [Any]) -> Void in
        bestAttemptContent.attachments = info
        self.contentHandler(bestAttemptContent)
    }, errorHandler: {(_ error: Error?) -> Void in
        self.contentHandler(bestAttemptContent)
    })
}

Once your app implementation is complete you can send images via our dashboard by uploading an image in the Lock Screen Media field of the message composer or by using the property attachments of the notification payload of the REST API.

One thing to take in account, is that iOS will limit the amount of time it will allow your app to download a lock screen image. If the user’s network speed is slow, big images will probably not have enough time to download during the allowed execution period and be discarded, resulting in a message being presented without the lock screen media. To optimize this, make sure you do not upload images bigger than 300KB so you can cater to any network conditions users might have.