Skip to content

OneSignal

Enable Remote Notification entitlement

In your Xcode project, go to Signing & Capabilities and enable the Remote Notification capability in the Background Modes section.

Create all Segments

This segment contains all users who have recently processed a background push notification (ctx_last_processed) and who have background push enabled (ctx_background_push_allowed).

This segment contains all users for which we should show a push notification, based on ContextSDK's prediction.

This segment contains all users who have seen a context-aware notification today (assuming you want to send a daily reminder to your users).

Create all Templates

This template is used for traditional push notifications that are not context-aware. This will be used as a fallback if the context-aware notification can't be shown. You can customize the content in this template as you like. Be sure to set ctx_context_aware to false.

This template is used to show a context-aware notification to the user for when it's a good moment to show the notification. You can customize the content in this template as you like. Be sure to set ctx_context_aware to true.

This template is used to wake up your app briefly to check if it's a good moment. The user won't see this notification. It's critical that you set the Content Available flag.

This template is used to clear any pending background push notifications before doing a fallback to a traditional push notification. This is a safety net to ensure that the user doesn't see the same notification twice.

Create all Journeys

This journey is triggered by the iOS app when the ctx_collapse_id and ctx_flow_name user tags are set, to then trigger actually showing the push notification.

This journey is triggered daily, and distinguishes between showing a context-aware notification or a traditional push notification depending on the user's segment.

At the end of the day, if the user hasn't seen a context-aware notification, a traditional push notification will be sent.

Update your AppDelegate

In your AppDelegate.swift, the following changes are needed:

didFinishLaunchingWithOptions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 
{
  // Let OneSignal know if background push is enabled for this device
  OneSignal.User.addTag(key: "ctx_background_push_allowed", value: UIApplication.shared.backgroundRefreshStatus == .available ? "true" : "false")

  // If you don't already have this line
  UNUserNotificationCenter.current().delegate = self

  // Your other code here

  return true
}
userNotificationCenter:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) 
{
    let userInfo = response.notification.request.content.userInfo

    if let custom = userInfo["custom"] as? [String: Any],
        let additionalData = custom["a"] as? [String: Any],
        var collapseId = additionalData["ctx_collapse_id"] as? String
    {
        if collapseId == "daily_reminder" {
            collapseId += "-\(dateFormatter.string(from: Date()))"
        } else { /* This is a one-off campaign */ }

        // You can call this for every notification, we automatically filter out the ones not relevant
        ContextManager.handleNotificationClicked(userInfo: userInfo, customCollapseId: collapseId)
    }
    completionHandler()
}
didReceiveRemoteNotification:
func application(_ application: UIApplication,
                  didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                  fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{
    // Update the OneSignal profile tag "ctx_last_processed" to be the current timestamp
    OneSignal.User.addTag(key: "ctx_last_processed", value: String(Int(Date().timeIntervalSince1970)))

    if application.applicationState == .background || application.applicationState == .inactive {
        if let custom = userInfo["custom"] as? [String: Any],
            let additionalData = custom["a"] as? [String: Any],
            var collapseId = additionalData["ctx_collapse_id"] as? String,
            let flowName = additionalData["ctx_flow_name"] as? String
        {
            if collapseId == "daily_reminder" {
                collapseId += "-\(dateFormatter.string(from: Date()))"
            } else { /* This is a one-off campaign */ }

            ContextManager.checkIfGoodMomentForPushNotification(
                flowName: flowName,
                collapseId: collapseId) { pushOutcome in
                    if pushOutcome == .shouldShowPush {
                        OneSignal.User.addTags([
                            "ctx_collapse_id": collapseId,
                            "ctx_flow_name": flowName
                        ])
                    }
                    completionHandler(.newData)
                }
        } else {
            print("Error parsing userInfo of notification: \(userInfo)")
            completionHandler(.failed)
        }
    } else {
        // Your app is already running, you can still decide to show the notification
    }
}

Explanation of the fields:

  • ctx_flow_name: The name of the flow (e.g. daily_reminder) describing what type of notification this is. You will get a custom model for each ctx_flow_name you use in your app
  • ctx_collapse_id: The collapse ID of the notification, which is used to only show a single notification per ctx_collapse_id at the perfect time. For a daily reminder you'd change this every day, for re-engagement campaigns you may change this every time you change your content.

Things to consider

  • The message will be delayed by around 5 seconds, due to the app detecting the real-world context, and one round-trip of network requests
  • Due to the nature of iOS push notifications, there is no guarantee that your app gets woken up for every notification. We recommend setting a ctx_last_processed attribute in your user profile to track the last time a notification was processed. This way, you can fallback to static notifications if the user hasn't opened the app in a while.

Go Live

Now all that's left is to ship your update to the App Store to start calibrating your model