Search code examples
swiftuipush-notificationunusernotificationcenter

SwiftUI handle push notifications tapped in a terminated state


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
            
        }

        
    }
    
}

Solution

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