Удаленное уведомление с изображением при завершении приложения iOS Swift 5

Вопрос или проблема

Я реализую Apple Push Notification в своем приложении, она реализована успешно, но я сталкиваюсь с проблемой: когда мое приложение завершено, мы не получаем никаких уведомлений, но если мое приложение открыто, мы успешно получаем уведомление с изображением.

Я также реализовал расширение службы уведомлений и расширение содержимого уведомлений.

Пожалуйста, помогите мне.

Спасибо.

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

Вот мой код AppDelegate:

import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Запрос разрешения на уведомления
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if let error = error {
                print("Ошибка при запросе разрешения на уведомления: \(error)")
            }
            if granted {
                DispatchQueue.main.async {
                    self.registerForPushNotifications(application)
                }
            }
        }

        UNUserNotificationCenter.current().delegate = self
        
        window = UIWindow(frame: UIScreen.main.bounds)
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let viewControllerIdentifier = Helper.getCurrentAuthentication().authenticationMeta != nil ? "HomeVC" : "LoginVc"
        let viewController = storyboard.instantiateViewController(withIdentifier: viewControllerIdentifier)
        window?.rootViewController = viewController
        window?.makeKeyAndVisible()
        
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenParts = deviceToken.map { String(format: "%02.2hhx", $0) }
        let deviceTokenString = tokenParts.joined()
        print("Токен устройства: \(deviceTokenString)")
        Helper.setFCMToken(fcmToken: deviceTokenString)
    }
    
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any]
    ) {
        print("Получено удаленное уведомление: \(userInfo)")

        // Извлечение заголовка и тела из userInfo
        var title = "Заголовок по умолчанию"
        var body = "Тело по умолчанию"

        if let aps = userInfo["aps"] as? [String: Any],
           let alert = aps["alert"] as? [String: Any] {
            title = alert["title"] as? String ?? title
            body = alert["body"] as? String ?? body
        }

        // Извлечение URL медиа из userInfo
        if let mediaUrl = userInfo["notificationMediaUrl"] as? String {
            print("URL медиа: \(mediaUrl)")
            Helper.setImageUrl(username: mediaUrl)

            // Загрузка данных изображения
            if let url = URL(string: mediaUrl) {
                URLSession.shared.dataTask(with: url) { data, response, error in
                    if let error = error {
                        print("Ошибка при загрузке изображения: \(error.localizedDescription)")
                        return
                    }

                    guard let data = data, let image = UIImage(data: data) else {
                        print("Не удалось конвертировать данные в изображение.")
                        return
                    }

                    // Создание временного URL для изображения
                    let temporaryDirectory = FileManager.default.temporaryDirectory
                    let imageURL = temporaryDirectory.appendingPathComponent(UUID().uuidString + ".jpg")

                    // Запись данных изображения во временный файл
                    do {
                        try data.write(to: imageURL)

                        // Создание вложения
                        let attachment = try UNNotificationAttachment(identifier: "imageAttachment", url: imageURL, options: nil)

                        // Создание содержимого уведомления
                        let content = UNMutableNotificationContent()
                        content.title = title // Использование извлеченного заголовка
                        content.body = body   // Использование извлеченного тела
                        content.sound = UNNotificationSound.default
                        content.categoryIdentifier = "notificationWithMedia"
                        content.attachments = [attachment]

                        // Создание триггера для уведомления (немедленно в данном случае)
                        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)

                        // Создание запроса
                        let request = UNNotificationRequest(identifier: UUID().uuidString,
                                                            content: content,
                                                            trigger: trigger)

                        // Добавление запроса уведомления в центр уведомлений
                        UNUserNotificationCenter.current().add(request) { error in
                            if let error = error {
                                print("Ошибка при добавлении уведомления: \(error.localizedDescription)")
                            }
                        }
                    } catch {
                        print("Ошибка при сохранении данных изображения: \(error.localizedDescription)")
                    }
                }.resume()
            }
        } else {
            print("URL медиа не найден.")
        }
    }


    

    private func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) async {
        
        print("Получено удаленное уведомление: \(userInfo)")
       
    }

    
    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        processNotificationContent(userInfo: userInfo, completionHandler: completionHandler)
    }
    
    private func processNotificationContent(userInfo: [AnyHashable: Any], completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let content = UNMutableNotificationContent()
        if let aps = userInfo["aps"] as? [String: Any] {
                if let alert = aps["alert"] as? [String: Any] {
                    content.title = alert["title"] as? String ?? "Заголовок по умолчанию"
                    content.body = alert["body"] as? String ?? "Тело по умолчанию"
                } else {
                    content.title = "Заголовок по умолчанию"
                    content.body = "Тело по умолчанию"
                }
            } else {
                content.title = "Заголовок по умолчанию"
                content.body = "Тело по умолчанию"
            }
        content.sound = UNNotificationSound.default

        if let mediaUrlString = userInfo["notificationMediaUrl"] as? String, let mediaUrl = URL(string: mediaUrlString) {
            URLSession.shared.dataTask(with: mediaUrl) { data, response, error in
                guard error == nil, let data = data else {
                    DispatchQueue.main.async {
                       // completionHandler([.alert, .sound])
                    }
                    return
                }

                // Создание временного URL для изображения
                let imageTempUrl = FileManager.default.temporaryDirectory.appendingPathComponent("image.jpg")

                do {
                    try data.write(to: imageTempUrl)
                    let attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: imageTempUrl, options: nil)
                    content.attachments = [attachment]
                } catch {
                    print("Ошибка при создании вложения: \(error)")
                }

                // Создание запроса с уникальным идентификатором
                let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
                UNUserNotificationCenter.current().add(request) { error in
                    if let error = error {
                        print("Ошибка при добавлении уведомления: \(error)")
                    }
                    DispatchQueue.main.async {
                        //completionHandler([.alert, .sound])
                    }
                }
            }.resume()
        } else {
            DispatchQueue.main.async {
                completionHandler([.alert, .sound])
            }
        }
    }

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo

        if let urlString = userInfo["url"] as? String, let url = URL(string: urlString) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
        
        if(!Helper.getImageUrl().isEmpty) {
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let viewControllerIdentifier = "HomeVC"
            let viewController = storyboard.instantiateViewController(withIdentifier: viewControllerIdentifier)
            window?.rootViewController = viewController
            window?.makeKeyAndVisible()
        }
        
       

        completionHandler()
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Регистрация APNs не удалась: \(error)")
    }

    private func registerForPushNotifications(_ application: UIApplication) {
        if #available(iOS 10, *) {
            UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
                if granted {
                    DispatchQueue.main.async {
                        application.registerForRemoteNotifications()
                    }
                }
            }
        } else {
            // Определите свои собственные действия
                   let viewAction = UNNotificationAction(identifier: "VIEW_ACTION",
                                                          title: "Посмотреть",
                                                          options: [])
                   
                   let category = UNNotificationCategory(identifier: "notificationWithMedia",
                                                         actions: [viewAction],
                                                         intentIdentifiers: [],
                                                         options: [])
            UNUserNotificationCenter.current().setNotificationCategories([category])
            let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
            UIApplication.shared.registerUserNotificationSettings(settings)
            application.registerForRemoteNotifications()
        }
    }
}

Вот мое расширение службы уведомлений:

import UserNotifications
import MobileCoreServices
import UniformTypeIdentifiers

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

               // Проверка URL медиа
               if let mediaUrlString = bestAttemptContent?.userInfo["notificationMediaUrl"] as? String,
                  let mediaUrl = URL(string: mediaUrlString) {
                  
                   downloadImage(from: mediaUrl) { attachment in
                       if let attachment = attachment {
                           bestAttemptContent?.attachments = [attachment]
                       }
                       contentHandler(bestAttemptContent!)
                   }
               } else {
                   contentHandler(bestAttemptContent!)
               }
           }

           private func downloadImage(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
               URLSession.shared.dataTask(with: url) { data, response, error in
                   guard let data = data, error == nil else {
                       completion(nil)
                       return
                   }

                   let temporaryDirectory = FileManager.default.temporaryDirectory
                   let fileURL = temporaryDirectory.appendingPathComponent(UUID().uuidString + ".jpg")

                   do {
                       try data.write(to: fileURL)
                       let attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: fileURL, options: nil)
                       completion(attachment)
                   } catch {
                       print("Ошибка при создании вложения: \(error)")
                       completion(nil)
                   }
               }.resume()
           }
       }

Ответ или решение

Поддержка Удаленных Уведомлений с Изображением в iOS при Закрытом Приложении на Swift 5

Реализация системы удаленных уведомлений с поддержкой изображений в приложении для iOS требует тщательного подхода, особенно если ваше приложение может быть закрыто (термином "terminated" в контексте iOS). Давайте разберем все необходимые шаги для корректной интеграции Apple Push Notification (APNs) и обеспечения работы уведомлений даже в случае, когда приложение закрыто.

Шаг 1: Настройка APNs

Для начала убедитесь, что вы правильно настроили Apple Push Notifications:

  1. Создание App ID: Войдите в свою учетную запись разработчика Apple и создайте App ID с включенной поддержкой Push Notifications.

  2. Создание сертификатов: Создайте и скачайте необходимые сертификаты для APNs.

  3. Настройка Notification Service Extension: Добавьте Notification Service Extension в ваш проект. Эта расширяемая служба будет обрабатывать ваши уведомления, когда они приходят, и добавлять к ним медиа-ресурсы (например, изображения).

Шаг 2: Реализация Notification Service Extension

Ваш класс NotificationService представляет собой UNNotificationServiceExtension. Этот класс автоматически вызывается, когда уведомление поступает. Убедитесь, что вы правильно загружаете изображение:

import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        // Проверка наличия ссылки на медиа
        if let mediaUrlString = bestAttemptContent?.userInfo["notificationMediaUrl"] as? String,
           let mediaUrl = URL(string: mediaUrlString) {

            downloadImage(from: mediaUrl) { attachment in
                if let attachment = attachment {
                    bestAttemptContent?.attachments = [attachment]
                }
                contentHandler(bestAttemptContent!)
            }
        } else {
            contentHandler(bestAttemptContent!)
        }
    }

    private func downloadImage(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                completion(nil)
                return
            }

            let temporaryDirectory = FileManager.default.temporaryDirectory
            let fileURL = temporaryDirectory.appendingPathComponent(UUID().uuidString + ".jpg")

            do {
                try data.write(to: fileURL)
                let attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: fileURL, options: nil)
                completion(attachment)
            } catch {
                completion(nil)
            }
        }.resume()
    }
}

Шаг 3: Реализация AppDelegate

В вашем AppDelegate необходимо настроить разрешения на отправку уведомлений и обработку полученных уведомлений. Убедитесь, что ваш код для регистрации на уведомления и обработки уведомлений включает следующее:

import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Запросить разрешение на отправку уведомлений
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            }
        }

        UNUserNotificationCenter.current().delegate = self
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // Код для обработки токена устройства
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // Код для обработки реакции на уведомление
        completionHandler()
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .sound, .badge])
    }
}

Шаг 4: Проверка Payload Уведомления

Когда вы отправляете уведомление через APNs, убедитесь, что ваше сообщение содержит ключ "notificationMediaUrl" с правильной URL-адресой изображения. Ваша JSON-структура уведомления может выглядеть так:

{
  "aps": {
    "alert": {
      "title": "Заголовок уведомления",
      "body": "Текст уведомления"
    },
    "badge": 1,
    "sound": "default"
  },
  "notificationMediaUrl": "https://example.com/image.jpg"
}

Примечания

  1. Работа в фоновом режиме: Убедитесь, что ваша служба может работать в фоновом режиме и будет автоматически запускаться для обработки входящих уведомлений.

  2. Тестирование: Всегда тщательно тестируйте поведение вашего приложения при закрытии и получении уведомлений, чтобы убедиться, что отображается изображение переданное в уведомлении.

Заключение

Следуя этим шагам, вы сможете успешно настроить получение удалённых уведомлений с изображением даже в том случае, если ваше приложение закрыто. Это обеспечит полноценный пользовательский опыт и позволит избежать многих распространённых ошибок при работе с уведомлениями в iOS.

Оцените материал
Добавить комментарий

Капча загружается...