Enable Push Capability
In the app target > Signing & Capabilities > add Push Notifications.
Without the capability, there will be error when registering for remote notification.
Registering Remote Notification
Use UserNotifications framework to request for push permission.
let notificationCenter = UNUserNotificationCenter.current()
let application = UIApplication.shared
let isGranted = try await notificationCenter.requestAuthorization(options: [.alert, .badge, .sound])
await application.registerForRemoteNotifications()
The code will trigger a system prompt requesting for push permission.
You must also call application.registerForRemoteNotifications()
, so as to fetch the APNS token.
With that, the app delegate will receive the APNS token:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%.2x", $0) }.joined()
print(token)
}
PITFALL: Always registerForRemoteNotifications() on launch
It’s not only when user grant permission.
Instead, you must registerForRemoteNotifications()
on every app launch, as the APNS could be updated.
Moreover, Firebase requires the APNS, then the FCM token and methods will work. Errors like these are pointing to a missing APNS token:
APNS device token not set before retrieving FCM Token for Sender ID ‘XXX’. Be sure to re-retrieve the FCM token once the APNS device token is set.
Declining request for FCM Token since no APNS Token specified. No APNS token specified before fetching FCM Token
The subscription operation failed due to an error getting the FCM token: Error Domain=com.google.fcm Code=505 “No APNS token specified before fetching FCM Token” UserInfo={NSLocalizedFailureReason=No APNS token specified before fetching FCM Token}.
Permission Alert
The alert is shown only once by the Operating System, when you first call requestAuthorization()
(subsequent call has no effect). You can give it a reason by adding to Info.plist Privacy - User Notifications Usage Description. But the system permission is easily overlooked.
Many apps provide their own “pre-permission UI” to tell user why they should allow push first, before triggering the system alert.
Tap on banner in foreground
When your app is in the foreground and a push notification is received, you need to handle them explicitly. There are 2 delegate methods that you need to implement.
extension AppDelegate: UNUserNotificationCenterDelegate {
// When the app receive notification
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Complete and show an alert banner + sound
completionHandler([.alert, .sound])
}
// When user tap on the notification
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if UIApplication.shared.applicationState == .active {
// App in foreground
} else {
// ...
}
}
}
Integrating with Firebase
Firebase is one of the most popular SDK with a backend service for sending push. The documentation is complete. Here are some notes on the FCM token (different from APNS).
In your Scheme > Arguments passed on launch, you can add -FIRDebugEnabled
to see console log. The effect will persist, so you need to change to -FIRDebugDisabled
to stop.
Console log:
Token fetch successful, token: abcd:1234
Alternatively, implement MessagingDelegate
.
Messaging.messaging().delegate = self
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(fcmToken ?? "-")")
}
}
With the FCM token, you can send test messages from Firebase Messaging web UI.
PITFALL: Firebase on macOS
Error fetching FCM registration token: Error Domain=com.google.iid Code=-34018 “(null)”
Solution: Add capability Keychain Sharing > Add com.google.iid
and com.google.iid.checkin
PITFALL: SwiftUI apps
If you have disabled method swizzling, or you are building a SwiftUI app, you will need to manually pass the APNS token to Firebase.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
PITFALL: FirebaseInstallation failed with API_KEY_SERVICE_BLOCKED
Push messaging will not work if certain Firebase API are “blocked”. As mentioned in their doc:
Note: If the API key that you use for Firebase services has “API restrictions” and you’re getting a API_KEY_SERVICE_BLOCKED or Forbidden 403 error, make sure that the API key has all the required APIs included in the key’s “API restrictions” allowlist.
Go to your credentials for your project, and check the API Keys. If you have multiple keys, the one used is that listed as API_KEY in GoogleService-Info.plist.
Under API restrictions, make sure that Firebase Installations API is selected.
Many different callbacks
I wrote a post in 2015 on the different delegate callbacks with background mode enabled.