I have a SwiftUI app with chat functionality that uses Firebase as its backend and Cloud Function to listen for new chat messages and notify users. The Cloud Function sends the notification to all devices of the logged-in user when the user has received a new chat message in one of the corresponding chat rooms. To navigate I have created a NotificationManager that helps the App to navigate through nested Views of the app if the user has tapped a notification.
Everything works, however when the user taps a notification after the app is terminated the navigation doesn't work. I guess it is because the navigation is tapped and its properties are changed before the user is restored...? I tried to look for solutions related to UIKit(since I didn't find any related to SwiftUI), but couldn't figure out a proper solution yet.
Above in my AppDelegate:
weak var notificationManager: NotificationManager?
User tapped notification:
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()
}
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?
}
LightWeight example of ContentView
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
}
}
}
Okay, I found the same problem in Swift, and the solution was to just delay the action of the notification tapped with one second. It feels more like a hack, I want to know exactly what happens asynchronously, but it works perfectly.
Here is the solution:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // <- here!!
self.notificationManager?.pageToNavigationTo = 1
self.notificationManager?.recievedNotificationFromChatID = chatID
}