AppDelegate & SceneDelegate Configuration

Keep your own app and notification delegates and forward the callbacks Pulsate needs. Covers apps with and without the UIScene lifecycle.

Pulsate needs a small number of callbacks from your app: push-token registration, background pushes and notification taps. You keep ownership of your UIApplicationDelegate and forward those callbacks to Pulsate.

📘

Requirements

iOS 15.0 or later. Xcode 16.3 or later (Swift 6.1+) to build against SDK 4.10.0.

👍

Foreground and background need no forwarding

Since 4.10.0, Pulsate detects when your app enters the foreground or background on its own, with or without scenes. You no longer forward applicationDidBecomeActive, applicationWillResignActive, applicationWillEnterForeground or applicationDidEnterBackground. If you already forward them, you can leave them in place - they are harmless.

Integration modes

Create the manager with getInstance(withAuthorizationData:withLaunchOptions:andPulsateNotificationDelegate:). Pulsate never replaces your app delegate; the one choice is who owns the UNUserNotificationCenter delegate:

andPulsateNotificationDelegateNotification delegateYou forward
false (recommended)Your appSections 2 and 3
truePulsateSection 2

🚧

Deprecated in 4.10.0

getInstance(withLaunchOptions:), getInstance(withAuthorizationData:withLaunchOptions:) and getInstance(withAuthorizationData:withLaunchOptions:withPulsateAppDelegate:andPulsateNotificationDelegate:) keep working but will be removed in a future major release, together with Pulsate acting as your app delegate. Calling them produces a compiler warning - if your build treats warnings as errors, this is a build break until you switch. See Migrating to the 4.10 initializer.

1. Initialize Pulsate

Create the manager in application(_:didFinishLaunchingWithOptions:), and make your app delegate the notification center's delegate before didFinishLaunching returns.

import UIKit
import UserNotifications
import PULPulsate

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var pulsateManager: PULPulsateManager?

    /// Pulsate's delegate proxy. Fetched on demand so it is always current.
    var pulsateSystemManager: (UIApplicationDelegate & UNUserNotificationCenterDelegate)? {
        PULPulsateFactory.getDefaultInstance()?.getPulsateSystemManager()
    }

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Must be set before this method returns. A notification tap that launches the app
        // is delivered as soon as launch finishes; with no delegate set, it is dropped.
        UNUserNotificationCenter.current().delegate = self

        do {
            let authData = try PULAuthorizationData(withAppId: "APP_ID", andAppKey: "APP_KEY")
            pulsateManager = try PULPulsateFactory.getInstance(
                withAuthorizationData: authData,
                withLaunchOptions: launchOptions,
                andPulsateNotificationDelegate: false
            )
        } catch {
            print("Pulsate init failed: \(error)")
        }
        return true
    }
}

getPulsateSystemManager() returns Pulsate's delegate proxy, typed (UIApplicationDelegate & UNUserNotificationCenterDelegate)?. Forward each callback below to it.

2. Forward the push callbacks (UIApplicationDelegate)

These stay on your app delegate whether or not your app uses scenes. Without them your push token is never registered with Pulsate.

extension AppDelegate {

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        pulsateSystemManager?.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        pulsateSystemManager?.application?(application, didFailToRegisterForRemoteNotificationsWithError: error)
    }

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

3. Forward notification presentation and taps (UNUserNotificationCenterDelegate)

Skip this section if you passed andPulsateNotificationDelegate: true.

Forward to Pulsate only the pushes Pulsate sent - those whose payload sender is "Pulsate" or "PulsateDebug". Handle every other push yourself: Pulsate does not call the completion handler for pushes it did not send.

extension AppDelegate: UNUserNotificationCenterDelegate {

    private func isPulsatePush(_ notification: UNNotification) -> Bool {
        let sender = notification.request.content.userInfo["sender"] as? String
        return sender == "Pulsate" || sender == "PulsateDebug"
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        if isPulsatePush(notification), let pulsate = pulsateSystemManager {
            pulsate.userNotificationCenter?(center, willPresent: notification, withCompletionHandler: completionHandler)
            return
        }
        // Not a Pulsate push - handle it yourself
        completionHandler([.banner, .list, .badge, .sound])
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        if isPulsatePush(response.notification), let pulsate = pulsateSystemManager {
            // Pulsate opens the campaign's URL, deeplink, card or feed
            pulsate.userNotificationCenter?(center, didReceive: response, withCompletionHandler: completionHandler)
            return
        }
        // Not a Pulsate push - handle it yourself
        completionHandler()
    }
}

🚧

If notification taps do nothing

If tapping a Pulsate push opens the app but not the campaign's link, check that UNUserNotificationCenter.current().delegate is set in didFinishLaunching and that didReceive forwards Pulsate pushes as above.

4. Scene-based apps (UIScene lifecycle)

Apps built with Xcode 27 must use the UIScene lifecycle (a UIApplicationSceneManifest in Info.plist). Pulsate's integration does not change: everything it needs stays on the app delegate, and your scene delegate forwards nothing to Pulsate.

CallbackWhere it goes
Push token, registration failure, background pushApp delegate (section 2)
UNUserNotificationCenter delegateApp delegate, set in didFinishLaunching (section 3)
Foreground / backgroundNothing to forward - Pulsate handles it

📘

Initialize Pulsate in the app delegate

Create the Pulsate manager in application(_:didFinishLaunchingWithOptions:), not in your scene delegate or after sign-in. A notification tap that launches the app is handled shortly after launch, and Pulsate must exist by then.

📘

Your own deeplinks

If a Pulsate campaign links to your app's own URL scheme, iOS delivers that link to your scene delegate, not your app delegate. See Deeplinks - Scene-based apps.

📘

Multiple windows (iPad)

Pulsate's sessions follow the whole app, not individual scenes: a session ends when the app's last scene goes to the background and starts when the first one comes back to the foreground.

Migrating to the 4.10 initializer

You call getInstance(...withPulsateAppDelegate: false, andPulsateNotificationDelegate: X) - remove the withPulsateAppDelegate: false argument. Nothing else changes.

You call getInstance(withLaunchOptions:), getInstance(withAuthorizationData:withLaunchOptions:), or pass withPulsateAppDelegate: true - Pulsate is currently your app delegate:

  1. Switch to getInstance(withAuthorizationData:withLaunchOptions:andPulsateNotificationDelegate:). Pass true to keep Pulsate as the notification delegate, as before, or false and forward as in section 3.
  2. Forward the push callbacks in section 2 - without this, your push token is never registered.
  3. Nothing else needs forwarding: not the foreground/background callbacks, application(_:open:options:), application(_:continue:restorationHandler:) or applicationWillTerminate(_:).

Related pages