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

Posted

技术标签:

【中文标题】没有 AppDelegate 的 SwiftUI 远程推送通知(Firebase 云消息传递)【英文标题】:SwiftUI remote push notifications without AppDelegate (Firebase Cloud Messaging) 【发布时间】:2020-12-25 00:50:17 【问题描述】:

我正在尝试在 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.
    

【问题讨论】:

为什么不推荐@UIApplicationDelegateAdaptor? 【参考方案1】:

我刚刚创建了一个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", $0) .joined()
        return hexString
    

【讨论】:

它有效,但在模拟器中却没有.. 你知道这是为什么吗? @Rexhin 我没有。您可以在模拟器上进行本地通知,甚至可以将通知从模拟器发送到您的设备。但模拟器无法接收来自您的设备的通知。当我需要测试某些东西时,我会告诉使用我的应用的朋友我需要确认这一点或那点。 @David 那么我们是否必须使用代理进行推送通知?我认为 SwiftUI 减少了 Delegate 和其他一些事情的痛苦。请帮帮我【参考方案2】:

Le_fretApp.initUIApplication.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()
    

而且,顺便说一句,我认为这个属性也应该引用同一个服务实例,即共享

var notificationsService = NotificationsService.Shared    // !!

【讨论】:

【参考方案3】:

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

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

var notificationsService = NotificationsService()

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

UIApplication.shared.delegate = NotificationsService.Shared

我不明白这会给您带来什么困扰,但这肯定不是一个好主意,而且可能会在幕后引起一些问题。

【讨论】:

以上是关于没有 AppDelegate 的 SwiftUI 远程推送通知(Firebase 云消息传递)的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将 AppDelegate 添加到独立的 watchOS SwiftUI 应用程序中?

SwiftUI 2 访问 AppDelegate

SwiftUI:为啥 ObservedObject 在 AppDelegate 中不起作用?

AppDelegate 和 ContentView() 之间的 SwiftUI 通信

如何在swiftUI中获得类似appdelegate的东西

SwiftUI中如何使用@EnvironmentObject在AppDelegate和SceneDelegate/Views之间共享数据