Handling iOS push notifications
Note: We Recommend taking advantage of our KnockAppDelegate to make managing your Push Notifications simpler.
Prerequisites
Before proceeding, ensure you've configured push notifications within your Knock account. For guidance on this initial setup, refer to our Push Notification Configuration Guide.
Step 1: Enabling Push Notifications in Your App
- Configure APNs in Your App:
- Open your project in Xcode.
- Navigate to your app target's Signing & Capabilities tab.
- Click the "+" capability button and add Push Notifications to enable Apple Push Notification service (APNs).
- Enable Background Modes:
- Still in the Signing & Capabilities tab, add the Background Modes capability.
- Check Remote notifications to allow your app to receive silent push notifications.
Step 2: Registering for Push Notifications
Implement the following in your AppDelegate or SceneDelegate to register for push notifications:
KnockAppDelegate:
If using the KnockAppDelegate
, this will be handled for you automatically.
Manually:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
class MyAppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { UNUserNotificationCenter.current().delegate = self // Will request push notification permissions, and will automatically register if perms are granted. Knock.shared.requestAndRegisterForPushNotifications() // Check if launched from the tap of a notification if let launchOptions = launchOptions, let userInfo = launchOptions[.remoteNotification] as? [String: AnyObject] { pushNotificationTapped(userInfo: userInfo) } return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // Register the token with Knock Task { let channelId = await Knock.shared.getPushChannelId() do { let _ = try await Knock.shared.registerTokenForAPNS(channelId: channelId, token: Knock.convertTokenToString(token: deviceToken)) } catch { // Handle error } } } }
Step 3: Updating the Message Status of a Push Notification.
If a push notification is sent via Knock, it will contain a knock_message_id
property that includes the corresponding message Id. This can then be used to update the message status.
KnockAppDelegate:
If using the KnockAppDelegate
, this will be handled for you automatically.
Manually:
1 2 3 4 5 6 7 8
class AppDelegate: UIResponder, UIApplicationDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { if let messageId = getMessageId(userInfo: notification.request.content.userInfo) { Knock.shared.updateMessageStatus(messageId: messageId, status: .seen) { _ in } } completionHandler(presentationOptions) } }
Step 4: Configuring Silent Push Notifications
Silent push notifications allow your app to update content in the background without alerting the user. Ensure that your Knock APNs message template has silent notifications enabled.

KnockAppDelegate:
1 2 3 4 5 6
class MyAppDelegate: KnockAppDelegate { override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Pull any information you need out of userInfo, and change the completionHandler value depending on your needs. completionHandler(.noData) } }
Manually:
1 2 3 4 5 6
class MyAppDelegate: UIResponder, UIApplicationDelegate { open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Pull any information you need out of userInfo, and change the completionHandler value depending on your needs. completionHandler(.noData) } }
Full Example
Here's the complete AppDelegate
implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class AppDelegate: KnockAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { Task { try? await Knock.shared.setup(publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose)) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } override func pushNotificationDeliveredInForeground(notification: UNNotification) -> UNNotificationPresentationOptions { let options = super.pushNotificationDeliveredInForeground(notification: notification) // Handle push notification here return [options] } override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // Handle silent push notification here completionHandler(.noData) } }
Using FCM in iOS
If you prefer to use Firebase Cloud Messaging (FCM) within your iOS app, you can follow the same steps outlined above for push notifications with a few modifications to your AppDelegate
.
Step 1: Configure Firebase
In your application(_:didFinishLaunchingWithOptions:)
method, add the following:
-
Initialize Firebase:
1
FirebaseApp.configure()
-
Set the Messaging Delegate:
1
Messaging.messaging().delegate = self
-
Setup Knock:
1 2 3 4 5 6 7
Task { try? await Knock.shared.setup( publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose) ) }
Step 2: Override Push Notification Registration Methods
Override the application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
method to prevent the KnockAppDelegate
from handling this automatically:
1
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {}
MessagingDelegate
Step 3: Implement Extend your AppDelegate
to conform to MessagingDelegate
and implement the messaging(_:didReceiveRegistrationToken:)
method:
1 2 3 4 5 6 7 8 9
extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { Task { if let channelId = await Knock.shared.getPushChannelId(), let token = fcmToken { let _ = try? await Knock.shared.registerTokenForAPNS(channelId: channelId, token: token) } } } }
Full Example
Here's the complete AppDelegate
implementation with FCM support:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
class AppDelegate: KnockAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Step 1: Configure Firebase and Knock FirebaseApp.configure() Messaging.messaging().delegate = self Task { try? await Knock.shared.setup( publishableKey: Utils.publishableKey, pushChannelId: Utils.apnsChannelId, options: .init(hostname: Utils.hostname, loggingOptions: .verbose) ) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // Step 2: Override registration for remote notifications override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {} override func pushNotificationTapped(userInfo: [AnyHashable : Any]) { super.pushNotificationTapped(userInfo: userInfo) if let deeplink = userInfo["link"] as? String, let url = URL(string: deeplink) { UIApplication.shared.open(url) } } override func pushNotificationDeliveredInForeground(notification: UNNotification) -> UNNotificationPresentationOptions { let options = super.pushNotificationDeliveredInForeground(notification: notification) return [options] } override func pushNotificationDeliveredSilently(userInfo: [AnyHashable : Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { completionHandler(.noData) } } // Step 3: Implement MessagingDelegate extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { Task { if let channelId = await Knock.shared.getPushChannelId(), let token = fcmToken { let _ = try? await Knock.shared.registerTokenForAPNS(channelId: channelId, token: token) } } } }