Вопрос или проблема
Я реализую 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:
-
Создание App ID: Войдите в свою учетную запись разработчика Apple и создайте App ID с включенной поддержкой Push Notifications.
-
Создание сертификатов: Создайте и скачайте необходимые сертификаты для APNs.
-
Настройка 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"
}
Примечания
-
Работа в фоновом режиме: Убедитесь, что ваша служба может работать в фоновом режиме и будет автоматически запускаться для обработки входящих уведомлений.
-
Тестирование: Всегда тщательно тестируйте поведение вашего приложения при закрытии и получении уведомлений, чтобы убедиться, что отображается изображение переданное в уведомлении.
Заключение
Следуя этим шагам, вы сможете успешно настроить получение удалённых уведомлений с изображением даже в том случае, если ваше приложение закрыто. Это обеспечит полноценный пользовательский опыт и позволит избежать многих распространённых ошибок при работе с уведомлениями в iOS.