0

First, how would I be able to check if a user has opened a notification? (in all cases, when phone is locked, when app is in foreground, and when app is in background)

If the phone is locked, or the app hasn't been started, is it as simple as checking launch options in application() in AppDelegate? Ex:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let notificationOption = launchOptions?[.remoteNotification]
}

For the other two cases (foreground & background), what functions would be able to check in those cases? And would they still go in AppDelegate?

Second, when I do set up functions to check if the app has been opened from a notification, how would I be able to navigate SwiftUI to a specific view? In this case, it would be simply a specific TabView Tab.

I currently initialize my tabViewModel in SceneDelegate, and set the default current tab there. For example, currentTab = 2.

TabView(selection: $tabViewModel.currentTab) {
   ...
}

user12533955
  • 489
  • 8
  • 20
  • 1
    This should help [Open a specific view when opening the App through a Notification](https://stackoverflow.com/questions/60306123/open-a-specific-view-when-opening-the-app-through-a-notification) – Asperi Jun 10 '20 at 17:19

2 Answers2

1

When app is foreground or background userNotificationCenter with UNUserNotificationCenterDelegate will be called. When app is not started, you can check notification with SceneDelegate scene connectionOptions.

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        print("open notification")
        let userInfo = response.notification.request.content.userInfo
        
        print(userInfo)
        completionHandler()
    }
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
   if let notificationResponse = connectionOptions.notificationResponse {
          // you can get notification here
        }        

        let contentView = ContentView()

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

[UPDATE] you don't need SceneDelegate scene connectionOptions. even when app is not started. userNotificationCenter supports when app is foreground, background, and app is not started. https://stackoverflow.com/a/60857140/12208004

user12208004
  • 1,704
  • 3
  • 15
  • 37
  • but how with iOS 14 `App`? – TruMan1 Nov 16 '20 at 17:23
  • @TruMan1 I've not tested ios 14, you may need to add `AppDelgate`, and `application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)` – user12208004 Nov 17 '20 at 12:29
  • @TruMan1 you can use `func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)` https://stackoverflow.com/a/60857140/12208004 – user12208004 Nov 18 '20 at 03:03
1

This worked well for deep navigation after the user tapped a chat notification. The chat notification comes from Firebase so I have to retrieve the ChatID from the notification to know which one I have to open. With that in mind, I also know that the selected tab on the tabBar for the messages is the Int value of 1.

Now I can create a manager that works as a observed singelton object that listens to each notification that a user has tapped.

Above in AppDelegate:

weak var notificationManager: NotificationManager?

User notification tapped:

    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID from userNotificationCenter didReceive: \(messageID)")
        }
        
        print("*** NotificationInfo: \(userInfo) ***")
        let info = userInfo as NSDictionary
        guard let chatID = info.value(forKey: "chatID") as? String else { return } // retrieving the ChatID from notification payload
        
        
        // Navigate to the room of the chatID when chat notification is tapped
        
        
        notificationManager?.pageToNavigationTo = 1 // navigate to messages view
        notificationManager?.recievedNotificationFromChatID = chatID // navigate to correct chat
        
        
        completionHandler()
    }
// MARK: - SwiftUI Lifecycle
@main
struct VitaliiApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    @StateObject var session = SessionStore()
    let notificationManager = NotificationManager()
    
    func setUpdNotificationManager() {
        appDelegate.notificationManager = notificationManager
    }
    
    var body: some Scene {
        WindowGroup {
            
            ContentView()
                .environmentObject(session)
                .environmentObject(notificationManager)
                .onAppear {
                    setUpdNotificationManager()
                }   
        }
       
        
    }
}

The NotificationManager:

class NotificationManager: ObservableObject {
    static let shared = NotificationManager()
    @Published var pageToNavigationTo : Int?
    @Published var recievedNotificationFromChatID: String?
}

My ContentView with onRecieve() that listens to the NotificationManager and navigates to my InboxMessageView if a notification is tapped.

struct ContentView: View {
    
    @State var selection: Int = 0
    @EnvironmentObject var session: SessionStore
    
    @State var activeChatID: String?
    
    let tabBarImageNames = ["person.3", "message", "person"]
    
    @EnvironmentObject private var notificationManager: NotificationManager
    

        
    var body: some View {
        
        ZStack {
            Color.black
                .ignoresSafeArea()
            
            VStack {
                
                ZStack {
                    
                    switch selection {
                    case 0:
                         NavigationView {
                            HomeView()
                        }                        
                    case 1:
                        NavigationView {
                            InboxMessagesView(user: session.userSession, activeChatID: $activeChatID)
                            
                        }
                        .accentColor(.white)
                   
                    default:
                        NavigationView {
                            ProfileView(session: session.userSession)
                        }
 
                    }
                    
                }
                
                Spacer()
                
                
                    HStack {
                        
                        ForEach(0..<3) { num in
                            Button {
                                selection = num
                            } label: {
                                Spacer()
                                
                                Image(systemName: tabBarImageNames[num])
                                    .padding(.top, 10)
                                    .font(.system(size: 20, weight: .medium))
                                    .foregroundColor(selection == num ? .red : .white.opacity(0.7))
                                Spacer()
                            }
                            
                        }
                        
                        
                    }
            }
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)
        .onReceive(notificationManager.$pageToNavigationTo) {
            guard let notificationSelection = $0 else  { return }
            self.selection = notificationSelection // navigates to page InboxMessagesView
            
        }
        .onReceive(notificationManager.$recievedNotificationFromChatID) {
            guard $0 != nil else { return }
            
            self.activeChatID = $0 // navigates to the correct chat that is associated with the chatID when the user taps on a chat notification
            
        }

        
    }
    
}

In the InboxMessagesView I just have a ForEach loop with all the current user chats in a NavigationLink where each link is tagged with their corresponding chatID. It navigates by itself to the right chat if the notification is tapped. I can add the code for that View too if you want to.

Thel
  • 396
  • 2
  • 8