当应用程序不是 运行 和点击推送通知时,如何调试远程推送通知?
How to debug remote push notification when app is not running and tap push notification?
当应用 运行 收到推送通知时,会调用 didReceive。
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
)
因此,当调用上述委托时,我会使用收到的有效负载显示一个屏幕。这里没有问题。
当应用不是 运行 并且用户点击通知时,它应该显示与上面相同的屏幕。它不起作用,因为我没有在 didFinishLaunchingWithOptions 中添加代码。
所以,然后我添加了以下代码-
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
......
if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
......
}
return true
}
但这不起作用,我无法调试,因为在调试模式下,我必须从后台终止应用程序并点击通知,但在这种情况下,调试器将无法工作。我尝试了替代方法,即显示警报,但随后警报也不起作用
let aps = remoteNotif["aps"] as? [AnyHashable: Any]
let string = "\n Custom: \(String(describing: aps))"
let string1 = "\n Custom: \(String(describing: remoteNotif))"
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
if var topController = application.windows.first?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
let ac = UIAlertController(title: string1, message: string, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
topController.present(ac, animated: true)
}
}
我该如何解决这个问题?
“但在这种情况下,调试器将无法工作”不是真的!您可以在启动时附加调试器,即使 Xcode 没有启动它。
编辑方案,然后在 运行 操作中的“信息”下,显示“启动”的位置,单击第二个单选按钮:“等待可执行文件启动。” 运行 应用;它没有启动。现在通过推送通知启动应用程序。调试器工作。
您必须创建一个 NotificationServiceExtension 并在那里处理负载
在XCode,
- Select 你的项目
- 从左下角开始select 添加目标
- 添加通知服务扩展
然后尝试做这样的事情。以下代码是针对 FCM 的,但您可以根据自己的 payload 进行修改。
例如-
import UserNotifications
import UIKit
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 bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title)"
guard let fcmOptions = bestAttemptContent.userInfo["fcm_options"] as? [String: Any] else {
contentHandler(bestAttemptContent)
return
}
guard let imageURLString = fcmOptions["image"] as? String else {
contentHandler(bestAttemptContent)
return
}
getMediaAttachment(for: imageURLString) { [weak self] (image, error) in
guard let self = self,
let image = image,
let fileURL = self.saveImageAttachment(image: image, forIdentifier: "attachment.png")
else {
// bestAttemptContent.body = "Error - \(String(describing: error))"
contentHandler(bestAttemptContent)
return
}
let imageAttachment = try? UNNotificationAttachment(
identifier: "image",
url: fileURL,
options: nil)
if let imageAttachment = imageAttachment {
bestAttemptContent.attachments = [imageAttachment]
}
contentHandler(bestAttemptContent)
}
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func saveImageAttachment(image: UIImage, forIdentifier identifier: String) -> URL? {
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryPath = tempDirectory.appendingPathComponent(
ProcessInfo.processInfo.globallyUniqueString,
isDirectory: true)
do {
try FileManager.default.createDirectory(
at: directoryPath,
withIntermediateDirectories: true,
attributes: nil)
let fileURL = directoryPath.appendingPathComponent(identifier)
guard let imageData = image.pngData() else {
return nil
}
try imageData.write(to: fileURL)
return fileURL
} catch {
return nil
}
}
private func getMediaAttachment(for urlString: String, completion: @escaping (UIImage?, Error?) -> Void) {
guard let url = URL(string: urlString) else {
completion(nil, NotificationError.cannotParseURL)
return
}
ImageDownloader.shared.downloadImage(forURL: url) { (result) in
switch result {
case .success(let image):
completion(image, nil)
case .failure(let error):
completion(nil, error)
}
}
}
}
enum NotificationError: Error {
case cannotParseURL
}
我已经通过实现 sceneDelegate willConnectTo
方法解决了这个问题。 didFinishLaunchingWithOptions
中不需要处理
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Remote notification response
if let response = connectionOptions.notificationResponse{
print(response.notification.request.content.userInfo)
}
....
}
够了
当应用 运行 收到推送通知时,会调用 didReceive。
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
)
因此,当调用上述委托时,我会使用收到的有效负载显示一个屏幕。这里没有问题。
当应用不是 运行 并且用户点击通知时,它应该显示与上面相同的屏幕。它不起作用,因为我没有在 didFinishLaunchingWithOptions 中添加代码。
所以,然后我添加了以下代码-
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
......
if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
......
}
return true
}
但这不起作用,我无法调试,因为在调试模式下,我必须从后台终止应用程序并点击通知,但在这种情况下,调试器将无法工作。我尝试了替代方法,即显示警报,但随后警报也不起作用
let aps = remoteNotif["aps"] as? [AnyHashable: Any]
let string = "\n Custom: \(String(describing: aps))"
let string1 = "\n Custom: \(String(describing: remoteNotif))"
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
if var topController = application.windows.first?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
let ac = UIAlertController(title: string1, message: string, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
topController.present(ac, animated: true)
}
}
我该如何解决这个问题?
“但在这种情况下,调试器将无法工作”不是真的!您可以在启动时附加调试器,即使 Xcode 没有启动它。
编辑方案,然后在 运行 操作中的“信息”下,显示“启动”的位置,单击第二个单选按钮:“等待可执行文件启动。” 运行 应用;它没有启动。现在通过推送通知启动应用程序。调试器工作。
您必须创建一个 NotificationServiceExtension 并在那里处理负载
在XCode,
- Select 你的项目
- 从左下角开始select 添加目标
- 添加通知服务扩展
然后尝试做这样的事情。以下代码是针对 FCM 的,但您可以根据自己的 payload 进行修改。
例如-
import UserNotifications
import UIKit
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 bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title)"
guard let fcmOptions = bestAttemptContent.userInfo["fcm_options"] as? [String: Any] else {
contentHandler(bestAttemptContent)
return
}
guard let imageURLString = fcmOptions["image"] as? String else {
contentHandler(bestAttemptContent)
return
}
getMediaAttachment(for: imageURLString) { [weak self] (image, error) in
guard let self = self,
let image = image,
let fileURL = self.saveImageAttachment(image: image, forIdentifier: "attachment.png")
else {
// bestAttemptContent.body = "Error - \(String(describing: error))"
contentHandler(bestAttemptContent)
return
}
let imageAttachment = try? UNNotificationAttachment(
identifier: "image",
url: fileURL,
options: nil)
if let imageAttachment = imageAttachment {
bestAttemptContent.attachments = [imageAttachment]
}
contentHandler(bestAttemptContent)
}
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func saveImageAttachment(image: UIImage, forIdentifier identifier: String) -> URL? {
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryPath = tempDirectory.appendingPathComponent(
ProcessInfo.processInfo.globallyUniqueString,
isDirectory: true)
do {
try FileManager.default.createDirectory(
at: directoryPath,
withIntermediateDirectories: true,
attributes: nil)
let fileURL = directoryPath.appendingPathComponent(identifier)
guard let imageData = image.pngData() else {
return nil
}
try imageData.write(to: fileURL)
return fileURL
} catch {
return nil
}
}
private func getMediaAttachment(for urlString: String, completion: @escaping (UIImage?, Error?) -> Void) {
guard let url = URL(string: urlString) else {
completion(nil, NotificationError.cannotParseURL)
return
}
ImageDownloader.shared.downloadImage(forURL: url) { (result) in
switch result {
case .success(let image):
completion(image, nil)
case .failure(let error):
completion(nil, error)
}
}
}
}
enum NotificationError: Error {
case cannotParseURL
}
我已经通过实现 sceneDelegate willConnectTo
方法解决了这个问题。 didFinishLaunchingWithOptions
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Remote notification response
if let response = connectionOptions.notificationResponse{
print(response.notification.request.content.userInfo)
}
....
}
够了