没有 AppDelegate 的 SwiftUI 远程推送通知(Firebase 云消息传递)

SwiftUI remote push notifications without AppDelegate (Firebase Cloud Messaging)

我正在尝试在 SwiftUI 2.0 中实现远程推送通知,但没有 AppDelegate。我知道我可以通过 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 提供一个,但我了解到不推荐这样做。

我已尝试通过 Firebase 云消息传递触发通知,但我没有收到任何测试通知。我刚收到允许通知的弹出窗口,仅此而已。

我没有收到任何错误之类的东西..什么都没发生。

我是不是漏掉了什么?

测试:

Firebase registration token: Optional("fwRsIKd7aUZeoLmmW5b4Zo:APA91bHrVvArS-mLZMEkdtzTxhRUuMWVgHNKXdLethAvR3Fa3h_RmAcdOz_jJzp1kDsEEtcvbnAFUn9eh9-cUSCTy9jBibbFoR2xngWdzWCvci1_iLQJtHtCjxk-C02CkVUDl7FX8esp")

这是我的代码:

import SwiftUI
import Firebase
import OSLog

@main
struct Le_fretApp: App {
    @StateObject var sessionStore = SessionStore()
    @StateObject var locationManagerService = LocationManagerService()
    @StateObject var userViewModel = UserViewModel()
    
    var notificationsService = NotificationsService()
    
    
    
    init() {
        UIApplication.shared.delegate = NotificationsService.Shared
        FirebaseConfiguration.shared.setLoggerLevel(.min)
        
        notificationsService.register()
        
        FirebaseApp.configure()
        
        notificationsService.setDelegate()
    }
    
    var body: some Scene {
        WindowGroup {
            TabViewContainerView()
                .environmentObject(sessionStore)
                .environmentObject(userViewModel)
                .environmentObject(locationManagerService)
                .onAppear {
                    sessionStore.listen()
                    userViewModel.listen()
                }
        }
    }
}

服务:

import Foundation
import UserNotifications
import OSLog
import UIKit

import Firebase


class NotificationsService: NSObject, UNUserNotificationCenterDelegate {
    static let Shared = NotificationsService()
    let gcmMessageIDKey = "gcmMessageIDKey"
    
    func register() {
        // For iOS 10 display notification (sent via APNS)
        UNUserNotificationCenter.current().delegate = self
        
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: {_, _ in })
        
        DispatchQueue.main.async {
          UIApplication.shared.registerForRemoteNotifications()
        }
    }
    

      // Receive displayed notifications for iOS 10 devices.
      func userNotificationCenter(_ center: UNUserNotificationCenter,
                                  willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo

        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)

        // Print message ID.
        if let messageID = userInfo[gcmMessageIDKey] {
          print("Message ID: \(messageID)")
        }

        // Print full message.
        print(userInfo)

        // Change this to your preferred presentation option
        completionHandler([[.alert, .sound]])
      }

      func userNotificationCenter(_ center: UNUserNotificationCenter,
                                  didReceive response: UNNotificationResponse,
                                  withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        // Print message ID.
        if let messageID = userInfo[gcmMessageIDKey] {
          print("Message ID: \(messageID)")
        }

        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)

        // Print full message.
        print(userInfo)

        completionHandler()
      }
}

extension NotificationsService: UIApplicationDelegate {
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
      // If you are receiving a notification message while your app is in the background,
      // this callback will not be fired till the user taps on the notification launching the application.
      // TODO: Handle data of notification

      // With swizzling disabled you must let Messaging know about the message, for Analytics
      // Messaging.messaging().appDidReceiveMessage(userInfo)

      // Print message ID.
      if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
      }

      // Print full message.
      print(userInfo)
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
      // If you are receiving a notification message while your app is in the background,
      // this callback will not be fired till the user taps on the notification launching the application.
      // TODO: Handle data of notification

      // With swizzling disabled you must let Messaging know about the message, for Analytics
      // Messaging.messaging().appDidReceiveMessage(userInfo)

      // Print message ID.
      if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
      }

      // Print full message.
      print(userInfo)

      completionHandler(UIBackgroundFetchResult.newData)
    }
}

extension NotificationsService: MessagingDelegate {
    func setDelegate() {
        Messaging.messaging().delegate = self
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      print("Firebase registration token: \(String(describing: fcmToken))")

      let dataDict:[String: String] = ["token": fcmToken ?? ""]
      NotificationCenter.default.post(name: Notification.Name("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.
    }
}

Le_fretApp.init 使用 UIApplication.shared 还为时过早,因为它还没有在那里初始化。

尝试在创建场景时(或在您认为合适且应用程序已初始化的其他地方)执行此操作。

@main
struct Le_fretApp: App {
  // ... other code here

    func createScene() -> some Scene {
        if nil == UIApplication.shared.delegate {
            UIApplication.shared.delegate = NotificationsService.Shared  // << !!
        }

        return WindowGroup {
            // ... your scene content here
        }
    }

    var body: some Scene {
        createScene()
    }

而且,顺便说一句,我认为这个 属性 也应该指代相同的服务实例,即 shared

var notificationsService = NotificationsService.Shared    // !!

我刚刚创建了一个 App Delegate。适用于本地和远程通知。

我有一个 PushNotificationManager 可以远程推送。每当我将数据发送到 Firebase(我正在使用 Firestore)时,我都会将 AppDelegate.fcmToken 传递给用户的 fcmToken 属性(每个用户在模型中都有一个),例如token: user.fcmToken.

class AppDelegate: NSObject, UIApplicationDelegate {
    
    private var gcmMessageIDKey = "gcm_message_idKey"
    static var fcmToken = String()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        FirebaseApp.configure()
        Messaging.messaging().delegate = self
        UNUserNotificationCenter.current().delegate = self
        registerForPushNotifications()
        
        return true
    }
    
    func registerForPushNotifications() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
            print("Permission granted: \(granted)")
            guard granted else { return }
            self?.getNotificationSettings()
        }
    }
    
    func getNotificationSettings() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            print("Notification settings: \(settings)")
            guard settings.authorizationStatus == .authorized else { return }
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
    
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
       
        AppDelegate.fcmToken = deviceToken.hexString
    }
    
    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("Failed to register: \(error.localizedDescription)")
    }
    
    func application(
        _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
       
        print(userInfo)
        completionHandler(.newData)
    }
}

扩展程序

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
    
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        let userInfo = notification.request.content.userInfo
        print("Will Present User Info: \(userInfo)")
        
        completionHandler([[.banner, .sound]])
    }
    
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void) {
        
        let userInfo = response.notification.request.content.userInfo
        
        if response.actionIdentifier == "accept" {
            print("Did Receive User Info: \(userInfo)")
            
            completionHandler()
        }
    }
}

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        let dataDict: [String: String] = [AppDelegate.fcmToken: fcmToken ?? ""]
        NotificationCenter.default.post(name: NSNotification.Name("FCMToken"), object: nil, userInfo: dataDict)

        // Note: This callback is fired at each app startup and whenever a new token is generated.
        AppDelegate.fcmToken = fcmToken!
    }
}

extension Data {
    var hexString: String {
        let hexString = map { String(format: "%02.2hhx", [=11=]) }.joined()
        return hexString
    }
}

@Rexhin,我不确定这是否是您遇到问题的原因,但我注意到您使用了两个 NotificationsService 实例。

您将在下一行中创建一个实例,然后在该实例上调用 register()。

var notificationsService = NotificationsService()

您正在使用 shared() 实例作为委托(init 的第一行):

UIApplication.shared.delegate = NotificationsService.Shared

我不明白这会给你带来什么麻烦,但这肯定不是一个好主意,也许它会在幕后引起一些问题。