没有 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.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()
而且,顺便说一句,我认为这个属性也应该引用同一个服务实例,即共享
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:为啥 ObservedObject 在 AppDelegate 中不起作用?
AppDelegate 和 ContentView() 之间的 SwiftUI 通信
SwiftUI中如何使用@EnvironmentObject在AppDelegate和SceneDelegate/Views之间共享数据