swiftswiftuiapple-push-notificationsappdelegate

How can I deep link from a notification to a screen in SwiftUI?


I am trying to set up deep linking in my app. I have set up the app as well as the push notifications. However, I am unable to complete the final link from where the AppDelegate receives the user click on a notification to the screen that I want to deep link to. I basically want to call viewRouter.goToFred() from the AppDelegate. I have set up a git repo here (https://github.com/cameronhenige/SwiftUITestDeepLink) with an app that has everything set up except for this final piece. Could somebody help me figure this out? Here are the relevant pieces of code. Thanks!



import SwiftUI

struct MainView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter

    var body: some View {
        NavigationView {

            List {

            ForEach(viewRouter.pets) { pet in
                NavigationLink(
                    destination: PetView(),
                    tag: pet,
                    selection: $viewRouter.selectedPet,
                    label: {
                        Text(pet.name)
                    }
                )
            }
                Button("Send Notification to Fred") {
                    viewRouter.sendNotification()
                }
                
                Button("Manually go to Fred") {
                    viewRouter.goToFred()
                }
            }
            

        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}



import SwiftUI

@main
struct ContentView: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            MainView().environmentObject(ViewRouter())

        }

}
    
}


import Foundation
import UserNotifications

class ViewRouter: ObservableObject {
    @Published var selectedPet: Pet? = nil
    @Published var pets: [Pet] = [Pet(name: "Louie"), Pet(name: "Fred"), Pet(name: "Stanley")]
    
    
    func goToFred() {
        self.selectedPet = pets[1]
    }
    
    func sendNotification() {
        let content = UNMutableNotificationContent()
        let categoryIdentifire = "Notification Type"
        
        content.title = "Go To Fred"
        content.body = "Click me to go to Fred."
        content.sound = UNNotificationSound.default
        content.badge = 1
        content.categoryIdentifier = categoryIdentifire
        
        let request = UNNotificationRequest(identifier: "identifier", content: content, trigger: nil)
        UNUserNotificationCenter.current().add(request) { (error) in
            if let error = error {
                print("Error \(error.localizedDescription)")
            }
        }
        
    }
    
}



import SwiftUI

struct PetView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter

    var body: some View {
        
        if let pet = viewRouter.selectedPet {
            Text(pet.name)
        } else {
            EmptyView()
        }
    }
}

struct PetView_Previews: PreviewProvider {
    static var previews: some View {
        PetView()
    }
}



import Foundation

struct Pet: Identifiable, Hashable {
    var name: String
    var id: String { name }

}



import Foundation
import UIKit

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    let notificationCenter = UNUserNotificationCenter.current()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    
        if #available(iOS 10.0, *) {
          // For iOS 10 display notification (sent via APNS)
          UNUserNotificationCenter.current().delegate = self

          let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
          UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: {_, _ in })
        } else {
          let settings: UIUserNotificationSettings =
          UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
          application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
        
        return true
    }

}

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
      withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
      let userInfo = notification.request.content.userInfo
        print("Notification created")

      // Change this to your preferred presentation option
      completionHandler([[.alert, .sound]])
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
      let userInfo = response.notification.request.content.userInfo
        
        print("user clicked on notification. Now take them to Fred.")
        //todo Somehow I want to call viewRouter.goToFred(), but I don't have access to that object in AppDelegate
        
      completionHandler()
    }

}


Solution

  • I try this approach and it works. Admittedly I dont know if this is the correct or suggested way.

    The appDelegate has to get reference to the same ViewRouter instance that MainView is using, and where ContentView has to hook the appDelegate with the ViewRouter instance.

    ContentView.swift:

    @main
    struct ContentView: App {
        
        let router = ViewRouter()
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
      
        var body: some Scene {
            WindowGroup {
                MainView()
                    .environmentObject(router)
                    .onAppear(perform:setUpAppDelegate)
            }
        }
        
        func setUpAppDelegate(){
            appDelegate.router = router
        }
    }
    

    AppDelegate.swift:

    weak var router: ViewRouter?
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
      let userInfo = response.notification.request.content.userInfo
        
        print("user clicked on notification. Now take them to Fred.")
        //todo Update the ViewRouter to
        router?.goToFred()
        
        completionHandler()
    }