How to support Programmatic Navigation / Deep Linking in SwiftUI?

How are we expected to support programmatic navigation and deep linking in SwiftUI?


In traditional UIKit applications, supporting deep linking usually goes as follows:

  1. Parse deep link URL
  2. Determine target UIViewController
  3. Instantiate target UIViewController
  4. Push target UIViewController onto root UINaviationController


This functionality relies on the push/pop instance methods present on UINavigationController.


In the current iteration of SwiftUI we have access to NavigationView and NavigationLink. A NavigationView must be present in the containing view for NavigationLinks to function. NavigationLinks are effectively buttons that must be given a body view and a destination view.


If our application supports deep links to 10 separate screens, right now we would have to use NaviationLinks init(_:destination:isActive:) initializer to create 10 separate NavigationLinks. These NaviationLinks would all be configured with an EmptyView, dynamic destination determined by a model and unique isActive Binding Bool. Then when we are ready to deep link, we would need to determine which screen we are navigating to, set some sort of state in the model that sets the correct destination View and flips the correct Binding Bool to trigger the NavigationLink.


This approach is highly coupled and doesn’t scale well in larger applications.


Another example where this approach causes complexity is supporting AB testing. We often have a single button that can go to two or more unique screens depending on which test the user is in.


I would expect to see a similar mechanism to that of UINavigationController that allows us to push a SwiftUI View onto the current NavigationView from a model.


SwiftUI is a very different approach to UI development / management and I’m sure we are all struggling to transition from a UIKit mindset. I’d love to see some guidance from the SwiftUI team as to how to cleanly build an application that allows navigation to any View from any View without coupling every View together.


https://feedbackassistant.apple.com/feedback/6973839

Post not yet marked as solved Up vote post of jehlertrip Down vote post of jehlertrip
8.8k views

Replies

Interested in this - not too sure how best to support deep linking.

One approach would be to have your destination trigger be not a boolean toggle but an enum type listing the possible destinations. You can then bind an if-statement into the isActive: parameter, something like the following:


struct ContentView: View {
    enum Destination: CaseIterable, Equatable {
        case first
        case second
        case third
    }
    @State var selectedDestination: Destination? = nil
    
    func isActiveBinding(for destination: Destination) -> Binding<Bool> {
        Binding(get: {
            guard let selected = self.selectedDestination else { return false }
            return destination == selected
        }, set: { self.selectedDestination = destination }) // not strictly necessary, but Binding expects it
    }

    func view(for destination: Destination) -> some View {
        switch destination {
            // ...
        }
    }

    var body: some View {
        NavigationView {
            // Invisible nav links
            ForEach(Destination.allCases, id: \.self) { dest in
                NavigationLink(destination: self.view(for: dest), isActive: self.isActiveBinding(for: dest)) {
                    EmptyView()
                }
            }
        }
    }
}

There is an article Programmatic navigation in SwiftUI project that covers exactly this topic.

after 2 years and with iOS 14, it looks like there is a solution in SwitUI for this: https://www.createwithswift.com/creating-a-custom-app-launch-experience-in-swiftui-with-deep-linking/

  • this one is even better: https://www.donnywals.com/handling-deeplinks-in-ios-14-with-onopenurl/

Add a Comment