Skip to main content
  1. Blog/

A SwiftUI Router

app programming Swift Talk Dim Sum
Phil Chu
Phil Chu
Making software since the 80s

One update I made to Talk Dim Sum this year was to add a custom router so I can escape the NavigationLink straightjacket.

My router is just a simple stack.

public class Router<T: Equatable> {

    public var path: [T] = []

    public init() {

    public func push(_ route: T) {

    public func pushNew(_ route: T) {
        if path.last != route {

    public func pop() {
        if !path.isEmpty {

    func clear() {
        path = []

It’s generic so I can use it in more than one app, each with a different set of routes. Here’s the one for my dim sum app, just listing the routes for bringing up a camera view and for displaying the image taken.

enum Route: Hashable {
    case mlCamera
    case mlImage(UIImage)

The enum cases have to be mapped to actual views.

struct DestinationView: View {

    let route: Route

    var body: some View {
        switch route {
        case .mlCamera:
        case let .mlImage(image):
            MLImageView(image: image)

Now I can instantiate the router at the top level of my app and pass it down as an environment object.

@State var router = Router<Route>()

    var body: some Scene {
        WindowGroup {

First supplying it to the NavigationStack, and within that establish the navigation destination.

 @Environment(Router<Route>.self) var router: Router<Route>

    var body: some View {
        @Bindable var router = router
        NavigationStack(path: $router.path) {
            .navigationDestination(for: Route.self) { route in
                DestinationView(route: route)

Now it’s ready to use, e.g. this line pushes a new view on the stack (if it’s not already there)