---------------------------------- | Push Notification Setup | ---------------------------------- | Status: Not Registered | | | | Device Token: | | | | [Register for Notifications] | ----------------------------------
Push notifications (APNs + FCM) in iOS Swift - Mini App: Build & Ship
import UIKit import UserNotifications import FirebaseCore import FirebaseMessaging class PushNotificationViewController: UIViewController { private let statusLabel = UILabel() private let tokenLabel = UILabel() private let registerButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground setupUI() // TODO: Configure Firebase here // TODO: Set Messaging delegate } private func setupUI() { statusLabel.text = "Status: Not Registered" statusLabel.translatesAutoresizingMaskIntoConstraints = false tokenLabel.numberOfLines = 0 tokenLabel.translatesAutoresizingMaskIntoConstraints = false registerButton.setTitle("Register for Notifications", for: .normal) registerButton.translatesAutoresizingMaskIntoConstraints = false registerButton.addTarget(self, action: #selector(registerTapped), for: .touchUpInside) view.addSubview(statusLabel) view.addSubview(tokenLabel) view.addSubview(registerButton) NSLayoutConstraint.activate([ statusLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40), statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), tokenLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 20), tokenLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), tokenLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), registerButton.topAnchor.constraint(equalTo: tokenLabel.bottomAnchor, constant: 40), registerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor) ]) } @objc private func registerTapped() { // TODO: Request notification permission and register for remote notifications } }
import UIKit import UserNotifications import FirebaseCore import FirebaseMessaging @main class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() UNUserNotificationCenter.current().delegate = self Messaging.messaging().delegate = self window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = PushNotificationViewController() window?.makeKeyAndVisible() return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenParts = deviceToken.map { data in String(format: "%02hhx", data) } let token = tokenParts.joined() print("APNs device token: \(token)") NotificationCenter.default.post(name: .didReceiveDeviceToken, object: token) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Failed to register: \(error)") NotificationCenter.default.post(name: .didFailToRegister, object: error.localizedDescription) } // MARK: UNUserNotificationCenterDelegate func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { return [.banner, .sound] } // MARK: MessagingDelegate func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { guard let fcmToken = fcmToken else { return } print("FCM token: \(fcmToken)") NotificationCenter.default.post(name: .didReceiveFCMToken, object: fcmToken) } } extension Notification.Name { static let didReceiveDeviceToken = Notification.Name("didReceiveDeviceToken") static let didFailToRegister = Notification.Name("didFailToRegister") static let didReceiveFCMToken = Notification.Name("didReceiveFCMToken") } class PushNotificationViewController: UIViewController { private let statusLabel = UILabel() private let tokenLabel = UILabel() private let registerButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground setupUI() addObservers() } private func setupUI() { statusLabel.text = "Status: Not Registered" statusLabel.translatesAutoresizingMaskIntoConstraints = false tokenLabel.numberOfLines = 0 tokenLabel.translatesAutoresizingMaskIntoConstraints = false registerButton.setTitle("Register for Notifications", for: .normal) registerButton.translatesAutoresizingMaskIntoConstraints = false registerButton.addTarget(self, action: #selector(registerTapped), for: .touchUpInside) view.addSubview(statusLabel) view.addSubview(tokenLabel) view.addSubview(registerButton) NSLayoutConstraint.activate([ statusLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40), statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), tokenLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 20), tokenLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), tokenLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), registerButton.topAnchor.constraint(equalTo: tokenLabel.bottomAnchor, constant: 40), registerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor) ]) } private func addObservers() { NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDeviceToken(_:)), name: .didReceiveDeviceToken, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didFailToRegister(_:)), name: .didFailToRegister, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didReceiveFCMToken(_:)), name: .didReceiveFCMToken, object: nil) } @objc private func registerTapped() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in DispatchQueue.main.async { if let error = error { self.statusLabel.text = "Status: Permission Error" self.tokenLabel.text = error.localizedDescription return } if granted { self.statusLabel.text = "Status: Permission Granted" UIApplication.shared.registerForRemoteNotifications() } else { self.statusLabel.text = "Status: Permission Denied" self.tokenLabel.text = "" } } } } @objc private func didReceiveDeviceToken(_ notification: Notification) { if let token = notification.object as? String { DispatchQueue.main.async { self.statusLabel.text = "Status: Registered with APNs" self.tokenLabel.text = "Device Token:\n\(token)" } } } @objc private func didFailToRegister(_ notification: Notification) { if let errorMessage = notification.object as? String { DispatchQueue.main.async { self.statusLabel.text = "Status: Registration Failed" self.tokenLabel.text = errorMessage } } } @objc private func didReceiveFCMToken(_ notification: Notification) { if let fcmToken = notification.object as? String { DispatchQueue.main.async { self.statusLabel.text = "Status: Registered with FCM" self.tokenLabel.text = "FCM Token:\n\(fcmToken)" } } } deinit { NotificationCenter.default.removeObserver(self) } }
This solution sets up push notifications for an iOS app using both APNs and Firebase Cloud Messaging (FCM). The AppDelegate initializes Firebase and sets itself as the delegate for notification center and messaging. It handles device token registration and errors, posting notifications to update the UI.
The PushNotificationViewController shows the registration status and token. When the user taps the register button, the app requests notification permission. If granted, it registers for remote notifications to get the APNs device token. The app listens for notifications about device token and FCM token updates and displays them on screen.
This approach keeps UI and notification logic separated and uses NotificationCenter to communicate between AppDelegate and the view controller. It also handles permission denial and errors gracefully, updating the status label accordingly.
---------------------------------- | Push Notification Setup | ---------------------------------- | Status: Registered with FCM | | | | FCM Token: | | abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 | | | | [Register for Notifications] | ----------------------------------
Practice
Solution
Step 1: Understand APNs role
APNs stands for Apple Push Notification service and is designed to send notifications to iOS devices.Step 2: Differentiate from other services
It does not handle authentication, data storage, or UI creation, only notification delivery.Final Answer:
To deliver push notifications to iOS devices -> Option CQuick Check:
APNs = notification delivery [OK]
- Confusing APNs with authentication services
- Thinking APNs manages app data
- Assuming APNs creates UI elements
Solution
Step 1: Identify correct class and method
The correct class is UNUserNotificationCenter and the method is requestAuthorization with options and completion handler.Step 2: Check syntax correctness
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in } uses the correct syntax with options array and closure parameters.Final Answer:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in } -> Option AQuick Check:
Correct class and method = UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in } [OK]
- Using wrong class names like UNNotificationCenter
- Omitting the completion handler closure
- Incorrect method names like requestPermission
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
}Solution
Step 1: Understand deviceToken processing
The deviceToken is converted byte-by-byte to a hexadecimal string representation.Step 2: Analyze print output
The print statement outputs "Device Token: " followed by the joined hex string, not the raw data or nil.Final Answer:
Device Token: <hexadecimal string> -> Option BQuick Check:
Device token converted to hex string = Device Token: <hexadecimal string> [OK]
- Expecting raw Data object printed
- Assuming deviceToken is nil
- Printing deviceToken without conversion
func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}Solution
Step 1: Verify permission request syntax
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in ... } uses correct class, method, options, and completion handler.Step 2: Confirm registration call
DispatchQueue.main.async ensures UIApplication.shared.registerForRemoteNotifications() runs on the main thread, which is correct practice.Final Answer:
No error, code is correct -> Option DQuick Check:
Standard registration flow followed correctly [OK]
- Thinking UNUserNotificationCenter delegate is required inside this registration function
- Claiming incorrect options array syntax
- Believing registerForRemoteNotifications should avoid DispatchQueue.main.async
Solution
Step 1: Understand FCM and APNs integration
FCM requires APNs credentials to send notifications to iOS devices through Apple's service.Step 2: Identify required configuration
You must upload the APNs authentication key or certificates to Firebase Console to enable this link.Final Answer:
Upload the APNs authentication key or certificates to the Firebase Console -> Option AQuick Check:
APNs key upload to Firebase = Upload the APNs authentication key or certificates to the Firebase Console [OK]
- Skipping APNs key upload to Firebase
- Relying only on Background Modes without APNs
- Ignoring FCM device token registration
Ready to prove it?
Pass the test and this pattern moves unknown
