Tariq
Tariq

Reputation: 9979

How to debug remote push notification when app is not running and tap push notification?

When app is running and it receive push notification then didReceive is called.

func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    )

So when above delegate is called then i present a screen using the payload i receive. There is no problem here.

When app is not running and user tap the notification then it should present the same screen like above. It's not working because i didn't added a code in didFinishLaunchingWithOptions.

So, then i added the following code -

func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
         ......        
    
            
        if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
         ......
        }
        
        return true
    }

But this is not working and i cannot debug because when in debug mode i have to kill the app from background and tap the notification but in this case the debugger won't work. I tried alternative method i.e. showing alert but then alert is also not working

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

How should i solve this problem ?

Upvotes: 5

Views: 2430

Answers (4)

Daniis1infiniteloop
Daniis1infiniteloop

Reputation: 235

Change your Launch from Automatically to Wait for executable to be launched and you should be able to debug your notification when the app is not running. enter image description here

Upvotes: 5

Tariq
Tariq

Reputation: 9979

I have solved it by implementing sceneDelegate willConnectTo method. There is no need to handle it in 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)
   }

   ....
} 

This is enough

Upvotes: 3

Rohit
Rohit

Reputation: 39

You will have to create a NotificationServiceExtension and handle the payload there

In XCode,

  1. Select your project
  2. From bottom left select Add Target enter image description here
  3. Add Notification Service Extension

And then try doing something like this. The below code is for FCM but you can amend it according to your own payload.

For example -

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
}

Upvotes: 0

matt
matt

Reputation: 534893

“but in this case the debugger won't work” Not true! You can attach the debugger on launch even though Xcode did not launch it.

Edit the scheme, and in the Run action, under Info, where it says Launch, click the second radio button: “Wait for the executable to be launched.” Run the app; it doesn’t launch. Now launch the app through the push notification. The debugger works.

Upvotes: 6

Related Questions