Удаленные push-уведомления SwiftUI без AppDelegate (Firebase Cloud Messaging)

#ios #swift #swiftui #firebase-cloud-messaging #apple-push-notifications

# #iOS #swift #swiftui #firebase-облако-обмен сообщениями #apple — push-уведомления

Вопрос:

Я пытаюсь реализовать удаленные push-уведомления в SwiftUI 2.0, а AppDelegate отсутствует. Я знаю, что могу предоставить его через @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate , но я узнал, что это не рекомендуется.

Я пытался запустить уведомление через Firebase Cloud Messaging, но я не получаю никаких тестовых уведомлений. Я только что получил всплывающее окно, чтобы разрешить уведомления, и все.

Я не получаю никаких ошибок или чего-то в этом роде.. на самом деле ничего не происходит.

Я что-то упустил?

Тест:

введите описание изображения здесь

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.
    }
}
 

Комментарии:

1. Почему @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: ".2hhx", $0) }.joined()
        return hexString
    }
}
 

Комментарии:

1. Это сработало,, но не в симуляторе.. Вы знаете, почему это так?

2. @Rexhin Я этого не делаю. Вы можете создавать локальные уведомления в симуляторе и даже отправлять уведомления из симулятора на свое устройство. Но симулятор не может получать уведомления с вашего устройства. Когда мне нужно что-то протестировать, я говорю другу, у которого есть мое приложение, что мне нужно подтвердить то или иное.

3. @David итак, нужно ли нам использовать делегат для push-уведомлений?. Я думал, что SwiftUI уменьшает сложность делегирования и других вещей. Пожалуйста, помогите мне немного

Ответ №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() в качестве делегата (первая строка инициализации):

 UIApplication.shared.delegate = NotificationsService.Shared
 

Я не понимаю, как это вас смущает, но это, безусловно, не очень хорошая идея, и, возможно, это вызывает некоторые проблемы за кулисами.