'How can I open a specific View in SwiftUI when a user opens a notification?
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) {
...
}
Solution 1:[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
Solution 2:[2]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | vebbis |
