Swizzling, Simulator, and Remote Push Notifications - iOS 14

688 views Asked by At

I'm getting strange behavior in iOS 14 with regards to remote notifications on iOS and Firebase Cloud Messaging (FCM).

DEVICE

First, I cannot send Test notifications to my device from the FCM console. I followed the documentation verbatim, and a few tutorials for good measure. Here's my AppDelegate:

import SwiftUI
import Firebase

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        FirebaseApp.configure()
        FirebaseConfiguration.shared.setLoggerLevel(.min)
        Messaging.messaging().delegate = self
        
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
            (granted, error) in
            print("Permission granted: \(granted)")
            // 1. Check if permission granted
            guard granted else {
                print("Permission not granted")
                return
            }
            // 2. Attempt registration for remote notifications on the main thread
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
        
        application.registerForRemoteNotifications()
        
        return true
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
        
        Messaging.messaging().appDidReceiveMessage(userInfo)
        
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        print(userInfo)
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        Messaging.messaging().appDidReceiveMessage(userInfo)
        
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        print(userInfo)
        
        completionHandler(UIBackgroundFetchResult.newData)
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Unable to register for remote notifications: \(error.localizedDescription)")
      }
}

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        let userInfo = notification.request.content.userInfo
        
        Messaging.messaging().appDidReceiveMessage(userInfo)
        
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        print(userInfo)
        
        completionHandler([[.banner, .sound]])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        
        Messaging.messaging().appDidReceiveMessage(userInfo)

        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        print(userInfo)
        
        completionHandler()
    }
}

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        print("Firebase registration token: \(fcmToken ?? "")")
        
        let dataDict:[String: String] = ["token": fcmToken ?? ""]
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "FCMToken"), object: nil, userInfo: dataDict)
        // TODO: If necessary send token to application server.
        // Note: This callback is fired at each app startup and whenever a new token is generated.
    }
    
    func application(application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken
    }
}

let gcmMessageIDKey = "gcm_Message_ID_Key"

Other things I've tried:

  1. Disabling method swizzling in the Info.pList (FirebaseAppDelegateProxyEnabled - Boolean - 0)

  2. Adding Messaging.messaging().appDidReceiveMessage(userInfo) to update the appropriate methods after disabling swizzling in the App Delegate.

  3. Sending App to background (can we test in foreground while app is running? Will it work in foreground if it works at all?)

SIMULATOR

Second, I am unable to send notifications to the simulator, unless I drag the aps file from Finder to said simulator. This aps file is a JSON file I can use for testing push notifications on simulators. Note: "alert" key has been deprecated in iOS 14.

{
    "Simulator Target Bundle": "com.Example.app",
    "aps": {
        "sound": "default",
        "badge": 1
    }
}

The terminal command utility looks like this

xcrun simctl push <device> com.Example example.apns

where the <device> is the simulator device identifier (you can also use "booted" in lieu of the device identifier if only one simulator is open) and example.apns is your file. I created my apns file in Atom.

This is what I get in the logs:

enter image description here

These are my reports from the FCM console:

enter image description here

1

There are 1 answers

1
David On

SOLVED

My implementation must've been wrong. I followed this verbatim and got it working on simulator and device. The simulator read, as a default error, remote notifications are not supported in the simulator.

There are a couple reasons why this might be the case:

  1. Bundle IDs from Pusher to Project don't match
  2. Not using the whole identifier e.g. 11FG89IK4-SHEN-13H4-AJD9-SN39FNS82JR for the simulator.

I also had an error in terminal Invalid device: [identifier] when I tried on a real device. The reason is I was using the device identifier in Windows > Devices and Simulator for my iPhone instead of the device token I get back in the console from this method:


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)")
}

I also used Push Notifications Tester instead of FCM. I don't know what I was doing wrong. I'm certain the Firebase tool works fine. Reasonably something on my end.