【问题标题】:SwiftUI remote push notifications without AppDelegate (Firebase Cloud Messaging)没有 AppDelegate 的 SwiftUI 远程推送通知(Firebase 云消息传递)
【发布时间】: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?

标签: ios swift swiftui firebase-cloud-messaging apple-push-notifications


【解决方案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
    

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

    【讨论】:

      猜你喜欢
      • 2017-11-18
      • 2016-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多