Navigating to the Right Page: A Guide to Handling Notifications in iOS Apps

Are you tired of having your users lost and confused when they interact with notifications in your iOS app? Do you want to make sure they are directed to the right page every time? Look no further! In this blog post, we will be diving into the world of ScreenCoordinator and NavigationLink, showing you how to handle navigation with ease and efficiency. Whether you're a developer just starting out or an experienced coder, this guide will give you the tools you need to make sure your users are always on the right page. So, let's get started and take control of your app's navigation today!

Prerequisites

  1. The user should already have push notifications set up in their iOS app. This guide will not cover the process of setting up push notifications. See this link for setting up push notifications.

  2. The user should have an iOS app created and ready to be modified. This guide will focus on the implementation of navigation in an existing iOS app. See this link for creating an iOS app.

It's important to note that this guide assumes that the user already has a basic understanding of Swift and SwiftUI, as well as the concepts of environment objects and NavigationLink. If the user is not familiar with these topics, it may be helpful to do some research or gain a better understanding before diving into this guide.

Step 1: Create the ScreenCoordinator Class

The first step in handling navigation with notifications is to create the ScreenCoordinator class. This class will act as an environment object that contains information about the page to navigate to. To create the class, you can copy and paste the following code:

final class ScreenCoordinator : ObservableObject {
    static let shared = ScreenCoordinator()
    @Published var selectedPushedItem : String?
    @Published var popToRoot = false

    enum PushedItem : String {
        case detailPage
        case profilePage
        case searchPage
    }
}

This class contains two @Published properties: selectedPushedItem and popToRoot. The selectedPushedItem property will be used to store the identifier of the page that the user should be directed to when they interact with a notification. The popToRoot property can be used to determine whether the user should be directed back to the root view before navigating to the selected page.

The ScreenCoordinator class also includes an enumeration of possible pages that the user can be directed to (detailPage, profilePage, and searchPage). You can add or remove items from this enumeration depending on the pages in your app.

Once the class is created, you can add it as an environment object to the ContentView of your app like so:

ContentView().environmentObject(ScreenCoordinator.shared)

Now, we have a foundation to navigate to a certain page of an iOS app upon the click of a notification.

Step 2: Set up ContentView

Your app will likely have different views within ContentView, but for the purposes of this demonstration, my ContentView will look like this:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var screenCoordinator : ScreenCoordinator

    var body: some View {
        NavigationView {
            VStack {
                List {
                   NavigationLink(destination: Page1View(), tag:                        ScreenCoordinator.detailPage, selection:         $screenCoordinator.selectedPushedItem) {
        Text("Detail Page")
                .tag(ScreenCoordinator.detailPage)
    }
    NavigationLink(destination: Page2View(), tag: ScreenCoordinator.profilePage, selection: $screenCoordinator.selectedPushedItem) {
        Text("Profile Page")
                .tag(ScreenCoordinator.profilePage)
    }
                }
            }    
        }

    }
}

This mimics the Navigation flow that your app uses to be able to go to different pages. The important part here is the NavigationLink. We give it the destination, tag and selection. Page1View() now has the ScreenCoordinator.detailPage as the tag. And the NavigationLink's selection is set to the screenCoordinator's selectedPushedItem. When selectedPushedItem changes, the NavigationLink will activate that selected page.

Step 3: Handle notifications from your AppDelegate

Next, you need to add an extension to your AppDelegate. The following code shows that:

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo

    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID: \(messageID)")
    }
    // Print full message.
    print(userInfo)
    if let pageId : String = userInfo["id"] as? String {
        // Send user to specific page depending on userInfo received
        NavigationUtil.popToRootView()
        screenCoordinator.selectedPushedItem = pageId

    }
    completionHandler()
  }
}

The above function is executed when the following happens:

1. The user taps the notification and the app is in the background

2. The app is woken up by iOS and the code is executed

3. The code above will be executed only if the app is in the background or if the app is killed

userInfo is all of the information that is passed in from the notification. In this case, the userInfo contains an "id" field that is the page we want to navigate to. NavigationUtil can be defined like so:

import Foundation
import UIKit

struct NavigationUtil {
  static func popToRootView() {
    findNavigationController(viewController: UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController)?
      .popToRootViewController(animated: true)
  }

  static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
    guard let viewController = viewController else {
      return nil
    }

    if let navigationController = viewController as? UINavigationController {
      return navigationController
    }

    for childViewController in viewController.children {
      return findNavigationController(viewController: childViewController)
    }

    return nil
  }
}

Finally, we see that screenCoordinator.selectedPushedItem is set to the notification id that was sent.

That's it! Now, when your users receive a push notification, upon tapping it from the background, they will be taken to the specified page.

Conclusion

In conclusion, handling navigation with notifications in an iOS app can seem daunting, but with the use of the ScreenCoordinator class and NavigationLink, it can be done with ease and efficiency. By creating the ScreenCoordinator class and adding it as an environment object, we are able to store information about the page to navigate to and use it to direct the user to the right page. By using NavigationLink, we are able to create a list of pages that the user can be directed to and handle the navigation accordingly. Remember to use the popToRoot property to decide whether the user should be directed back to the root view before navigating to the selected page. By following these steps, you will be able to improve the user experience in your app and make sure that your users are always on the right page.