SDK

Remote Notifications

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

Notificare supports 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.

Enabling Notifications

In order to enable the device to receive notifications, all that you need to do is invoke a single method.

Notificare.shared.push().enableRemoteNotifications { result in

}

Typically, the step above is done during some form of user onboarding. When the user already went through that flow, we can automatically enable notifications when the onReady event is triggered, be that via the NotificareDelegate or a Notificare.OnReadyListener.

func notificare(_ notificare: Notificare, onReady application: NotificareApplication) {
    if Notificare.shared.push().hasRemoteNotificationsEnabled {
        Notificare.shared.push().enableRemoteNotifications { result in

        }
    }
}

As shown above, you can check if the user has previously enabled remote notifications. Additionally, you can check if the user has disabled notifications in the System Settings.

Notificare.shared.push().allowedUI

Receiving Notifications

Before you can start receiving push notifications, you will need to set the capabilities in your project. Go to your app's target, click in the tab Capabilities and add both Push Notifications and Background Modes / Remote Notifications as shown below.

xcode capabilities push

Authorization options

Our SDK allows you to define which notification's authorization options you would like to request from your user. This is an optional step, if not defined we will register UNAuthorizationOptions.alert, UNAuthorizationOptions.badge and UNAuthorizationOptions.sound by default. To define authorization options please add the following to your AppDelegate and customise as needed:

if #available(iOS 13.0, *) {
    Notificare.shared.push().authorizationOptions = [.alert, .badge, .sound, .providesAppNotificationSettings, .provisional, .announcement]
} else if #available(iOS 12.0, *) {
    Notificare.shared.push().authorizationOptions = [.alert, .badge, .sound, .providesAppNotificationSettings, .provisional]
} else {
    Notificare.shared.push().authorizationOptions = [.alert, .badge, .sound]
}

Please note the following authorization options are available: UNAuthorizationOptions.announcement first introduced in iOS 13. The options UNAuthorizationOptions.providesAppNotificationSettings and UNAuthorizationOptions.provisional are available since iOS 12. The option UNAuthorizationOptions.announcement will allow Siri to automatically read out messages over AirPods, the UNAuthorizationOptions.provisional option will register for notifications with provisional authorization, this means that users will not be prompted to with the permission dialog to accept notifications. Although this might be a great way to opt-in users to remote notifications, any message you sent afterwards will be delivered quietly. Messages delivered quietly will not be shown in the lock screen or play a sound.

Also note that if you implement the UNAuthorizationOptions.providesAppNotificationSettings option, notifications from your app will display a button in both the Instant Tuning menu and Notification Settings. The purpose of that button is to provide users a shortcut to your app settings where they can fine-tune which kind of notifications they would like to receive. Implementing such settings views is highly recommended as it could be the reason between allowing your app to keep display notifications or being mute completely. If you do implement this option it is mandatory that you implement the following NotificarePushDelegate method:

func notificare(_ notificare: NotificarePush, shouldOpenSettings notification: NotificareNotification?) {
    // Deep link to your settings view.
}

This will give you the opportunity to present your users with the in-app settings view where you should allow them to customize what kind of notifications they should receive. If the user clicked the button from a specific notification, you will receive that object too. When it comes from the Notification Settings, that object will be nil.

Presentation options

Optionally, you can enable presentation options when your app is in the foreground. This will allow you to show a small banner on the top of your app, play a sound or badge your app, whenever a notification arrives, and your app is being used. By default, our library will set this to UNNotificationPresentationOptions = [], but you can change this behaviour to match your needs:

if #available(iOS 14.0, *) {
    Notificare.shared.push().presentationOptions = [.banner, .sound, .badge]
} else {
    Notificare.shared.push().presentationOptions = [.alert, .sound, .badge]
}

Category options

If you are using Rich Push Templates you can define category options. These options can define how your notifications and actions will behave. The options UNNotificationCategoryOptions.customDismissAction and UNNotificationCategoryOptions.allowInCarPlay were introduced in iOS 10, the options UNNotificationCategoryOptions.hiddenPreviewsShowTitle and UNNotificationCategoryOptions.hiddenPreviewsShowSubtitle were introduced in iOS 11 and the option UNNotificationCategoryOptions.allowAnnouncement was first introduced in iOS 13. By default, in iOS 10 we will use UNNotificationCategoryOptions.customDismissAction and in iOS 11 or higher UNNotificationCategoryOptions.customDismissAction and UNNotificationCategoryOptions.hiddenPreviewsShowTitle, but you can change this behaviour to match your needs:

if #available(iOS 13.0, *) {
    Notificare.shared.push().categoryOptions = [.customDismissAction, .hiddenPreviewsShowTitle, .allowAnnouncement]
} else if #available(iOS 11.0, *) {
    Notificare.shared.push().categoryOptions = [.customDismissAction, .hiddenPreviewsShowTitle]
} else {
    Notificare.shared.push().categoryOptions = [.customDismissAction]
}

Listening to Received Notifications

Once you're receiving notifications in your app, we can dive deeper and fully understand how they are handled. If you want to be notified incoming notifications in your NotificarePushDelegate instance, for example to add a badge to in your application, you can implement the following method.

extension AppDelegate: NotificarePushDelegate {
    func notificare(_ notificare: NotificarePush, didReceiveNotification notification: NotificareNotification) {

    }
}

Presenting notifications

A notification can be opened by either tapping the actual notification or by tapping an action inside the notification. We will emit an event in each case to the NotificarePushDelegate.

extension AppDelegate: NotificarePushDelegate {
    func notificare(_ notificare: NotificarePush, didOpenNotification notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePush, didOpenAction action: NotificareNotification.Action, for notification: NotificareNotification) {

    }
}

To handle the events above, you can take the managed approach and use our NotificarePushUI module which takes care of all the events and UI types as well as actions, or you can fully take over and present them however you prefer. However, be aware if you take the non-managed approach as you will have to deal with all aspects and types of presenting the notifications, including the events needed to show the user's engagement in our Dashboard.

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 Info.plist file:

ios camera privacy plist

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

The code below illustrates how this works when using the managed approach.

extension AppDelegate: NotificarePushDelegate {
    func notificare(_ notificare: NotificarePush, didOpenNotification notification: NotificareNotification) {
        let controller: UIViewController = /* find your desired controller to present the UI in */

        Notificare.shared.pushUI().presentNotification(notification, in: controller)
    }

    func notificare(_ notificare: NotificarePush, didOpenAction action: NotificareNotification.Action, for notification: NotificareNotification) {
        let controller: UIViewController = /* find your desired controller to present the UI in */

        Notificare.shared.pushUI().presentAction(action, for: notification, in: controller)
    }
}

Additionally, when using the managed approach, you can listen to Notification lifecycle events and perform any additional steps you may require. Let your AppDelegate implement the NotificarePushUIDelegate and add following methods as needed.

extension AppDelegate: NotificarePushUIDelegate {
    // MARK: - Notifications

    func notificare(_ notificare: NotificarePushUI, willPresentNotification notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didPresentNotification notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didFinishPresentingNotification notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didFailToPresentNotification notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didClickURL url: URL, in notification: NotificareNotification) {

    }

    // MARK: - Actions

    func notificare(_ notificare: NotificarePushUI, willExecuteAction action: NotificareNotification.Action, for notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didExecuteAction action: NotificareNotification.Action, for notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didNotExecuteAction action: NotificareNotification.Action, for notification: NotificareNotification) {

    }

    func notificare(_ notificare: NotificarePushUI, didFailToExecuteAction action: NotificareNotification.Action, for notification: NotificareNotification, error: Error?) {

    }

    func notificare(_ notificare: NotificarePushUI, shouldPerformSelectorWithURL url: URL, in action: NotificareNotification.Action, for notification: NotificareNotification) {

    }
}

If you are considering supporting non-HTTPS pages when using the Webpage notification type you will need to also declare a more permissive ATS policy as follows:

declare ats in plist

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 a 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 Info.plist and be sure to update the properties to match yours:

ios url schemes

If you are planning to handle Dynamic Links, you must also add all the domain prefixes you've created:

xcode associated domains

But of course you will want to show something to the user based on that URL. That is also easily done by implementing the following in the AppDelegate:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard let url = userActivity.webpageURL else {
        return false
    }

    return Notificare.shared.handleDynamicLinkUrl(url)
}

Another common example where you will make use of deep links, would be to take proper action whenever users click in hypertext links in a Notificare's HTML or Web Page notification type. This is done by first declaring all the URL schemes that you want to handle in your NotificareOptions.plist:

<plist version="1.0">
<dict>
	  <key>URL_SCHEMES</key>
	  <array>
		    <string>com.example</string>
        <string>com.example2</string>
        <string>com.example3</string>
	  </array>
</dict>
</plist>

Any click in an HTML or Web Page notification type, would be intercepted by our library and trigger the method didClickURL from NotificarePushUIDelegate.

Inbox

With our library it's extremely easy to implement an in-app inbox. Implementing an inbox increases considerably the engagement rate 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 any view controller of your app. The inbox is available via the NotificareInbox module. The inbox is exposed via the NotificareInboxDelegate, so it is really easy to hook up your view controller to a list of inbox items, for example:

extension InboxViewController: NotificareInboxDelegate {
    func notificare(_ notificare: NotificareInbox, didUpdateInbox items: [NotificareInboxItem]) {
        // Do something with the items.
    }
}

In the same way, you can listen for the number of unread messages.

extension InboxViewController: NotificareInboxDelegate {
    func notificare(_ notificare: NotificareInbox, didUpdateBadge badge: Int) {
        // Do something with the badge.
    }
}

If you want to get the data at any point in time, you can still get the items and the badge in the inbox.

// Items
Notificare.shared.inbox().items

// Badge
Notificare.shared.inbox().badge

Managing inbox items

Assuming that you hold a reference to an item, to open an inbox item you would simply do something like this:

let item: NotificareInboxItem

Notificare.shared.inbox().open(item) { result in

}

To remove items from the inbox you would invoke the following method:

let item: NotificareInboxItem

Notificare.shared.inbox().remove(item) { result in

}

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

let item: NotificareInboxItem

Notificare.shared.inbox().markAsRead(item) { result in

}

Or mark all messages as read by invoking the following method:

Notificare.shared.inbox().markAllAsRead { result in

}

To remove all items in the inbox you would do the following:

Notificare.shared.inbox().clear {

}

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.

With the package manager of your choice, add the NotificareNotificationServiceExtensionKit dependency to your app and proceed to implement it.

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

In your newly created implementation file, in the didReceive method, you must add the following in order to display whatever you end as the lock screen media of your messages:

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    NotificareNotificationServiceExtension.handleNotificationRequest(request) { result in
        switch result {
        case let .success(content):
            contentHandler(content)

        case let .failure(error):
            print("Failed to handle the notification request.\n\(error)")
            contentHandler(request.content)
        }
    }
}

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.

Notifications from Unknown Sources

In some apps it is possible you're also using other providers to send remote notifications, when that is the case Notificare will recognize an unknown notification and trigger a delegate method that you can use to further handle that notification. To be notified whenever that happens, implement the following methods of the NotificarePushDelegate in your AppDelegate.

extension AppDelegate: NotificarePushDelegate {
    // MARK: - Unknown Notifications

    func notificare(_ notificare: NotificarePush, didReceiveUnknownNotification userInfo: [AnyHashable: Any]) {

    }

    func notificare(_ notificare: NotificarePush, didOpenUnknownNotification notification: [AnyHashable: Any]) {

    }

    // MARK: - Unknown Actions

    func notificare(_ notificare: NotificarePush, didOpenUnknownAction action: String, for notification: [AnyHashable: Any], responseText: String?) {

    }
}