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,applicationWillEnterForegroundorapplicationDidEnterBackground. 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:
andPulsateNotificationDelegate | Notification delegate | You forward |
|---|---|---|
false (recommended) | Your app | Sections 2 and 3 |
true | Pulsate | Section 2 |
Deprecated in 4.10.0
getInstance(withLaunchOptions:),getInstance(withAuthorizationData:withLaunchOptions:)andgetInstance(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().delegateis set indidFinishLaunchingand thatdidReceiveforwards 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.
| Callback | Where it goes |
|---|---|
| Push token, registration failure, background push | App delegate (section 2) |
UNUserNotificationCenter delegate | App delegate, set in didFinishLaunching (section 3) |
| Foreground / background | Nothing 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:
- Switch to
getInstance(withAuthorizationData:withLaunchOptions:andPulsateNotificationDelegate:). Passtrueto keep Pulsate as the notification delegate, as before, orfalseand forward as in section 3. - Forward the push callbacks in section 2 - without this, your push token is never registered.
- Nothing else needs forwarding: not the foreground/background callbacks,
application(_:open:options:),application(_:continue:restorationHandler:)orapplicationWillTerminate(_:).

