如何在应用程序未运行并点击推送通知时调试远程推送通知?

Posted

技术标签:

【中文标题】如何在应用程序未运行并点击推送通知时调试远程推送通知?【英文标题】:How to debug remote push notification when app is not running and tap push notification? 【发布时间】:2021-05-10 07:23:46 【问题描述】:

当应用程序运行并收到推送通知时,会调用 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)
                
            

我应该如何解决这个问题?

【问题讨论】:

你有后台模式功能吗? @ElTomato - 这有什么关系?因为我正在点击通知。根据苹果的说法,后台模式是-“应用程序提供的需要它在后台运行的服务”。后台没有运行任何东西。应用正在接收推送,我只需点击通知即可打开应用。 你写过“当应用程序没有运行并且用户点击通知时,它应该呈现与上面相同的屏幕”,但如果你这么说...... 【参考方案1】:

“但在这种情况下,调试器将无法工作”不是真的!即使 Xcode 没有启动它,您也可以在启动时附加调试器。

编辑方案,然后在“运行”操作中,在“信息”下的“启动”处,单击第二个单选按钮:“等待启动可执行文件”。运行应用程序;它没有启动。现在通过推送通知启动应用程序。调试器工作正常。

【讨论】:

见我的biteinteractive.com/tricks-for-testing-external-links 谢谢让我看看。 11年来从未使用过这个技巧。如果在 xcode 未运行时可以激活调试器,那就太棒了。我只是想测试一下为什么 launchOptions 在 didFinishLaunchingWithOptions 中不起作用【参考方案2】:

您必须创建一个 NotificationServiceExtension 并在那里处理有效负载

在 XCode 中,

    选择您的项目 从左下角选择添加目标 添加通知服务扩展

然后尝试做这样的事情。以下代码适用于 FCM,但您可以根据自己的有效负载进行修改。

例如-

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

【讨论】:

【参考方案3】:

我已经通过实现 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)
   

   ....
 

这就够了

【讨论】:

以上是关于如何在应用程序未运行并点击推送通知时调试远程推送通知?的主要内容,如果未能解决你的问题,请参考以下文章

如何在快速接收推送通知时进行服务器调用?

当推送通知到达(未收到)设备时调用委托

当应用程序处于活动状态时,iOS 处理推送通知点击

当应用程序在后台运行时无法处理推送通知,接收推送但未点击横幅或警报

如何在 iOS 应用中检索和处理远程推送通知内容

iOS远程推送点击消息跳转到指定页面