SwiftUI Navigation Simplified: Custom Routing Guide

Custom Basic Navigation

enum AppRoute: Hashable, Codable {
    case first
    case second
    case third
    case fourth
    case fifth
}

class Router: ObservableObject {
    @Published private var stack: [AppRoute] = [.first]

    func push(_ screen: AppRoute) {
        stack.append(screen)
    }

    func pop() -> AppRoute? {
        return stack.popLast()
    }

    func popTo(_ route: AppRoute) {
        if let index = stack.firstIndex(of: route) {
            stack = Array(stack.prefix(upTo: index + 1))
        }
    }

    func currentScreen() -> AppRoute {
        return stack.last!
    }
    
    func reset(to route: AppRoute) {
        stack = [route]
    }
}
@main
struct RoutingInSwiftUIApp: App {
    @StateObject private var navigationStack = Router()

    var body: some Scene {
        WindowGroup {
            NavigationStack {
                VStack {
                    switch navigationStack.currentScreen() {
                    case .first:
                        FirstScreen()
                    case .second:
                        SecondScreen()
                    case .third:
                        ThirdScreen()
                    case .fourth:
                        FourthScreen()
                    case .fifth:
                        FifthScreen()
                    }
                }
            }
            .environmentObject(navigationStack)
        }
    }
}

First Screen

import SwiftUI

struct FirstScreen: View {
    @EnvironmentObject var navigationStack: Router

    var body: some View {
        ZStack {
            Color.red
                .ignoresSafeArea()
            
            VStack {
                Button(action: {
                    navigationStack.push(.second)
                }) {
                    Text("First Screen")
                        .font(.largeTitle)
                        .bold()
                }
            }

            Spacer()
        }
    }
}

#Preview {
    FirstScreen()
}

Second Screen

import SwiftUI

struct SecondScreen: View {
    @EnvironmentObject var navigationStack: Router

    var body: some View {
        ZStack {
            Color.orange
                .ignoresSafeArea()
            
            VStack {
                HStack {
                    Button {
                        _ = navigationStack.pop()
                    } label: {
                        Image("back")
                            .resizable()
                            .frame(width: 25, height: 25)
                            .padding(.leading)
                    }
                    Spacer()
                }
                .padding(.top, 20)
                
                Spacer()
            }
            
            VStack{
                Button(action: {
                    navigationStack.push(.third)
                }) {
                    Text("Second Screen")
                        .font(.largeTitle)
                        .bold()
                }
            }
        }
    }
}

#Preview {
    SecondScreen()
}

Third Screen

import SwiftUI

struct ThirdScreen: View {
    @EnvironmentObject var navigationStack: Router
    var body: some View {
        ZStack {
            Color.green
                .ignoresSafeArea()
            
            VStack {
                HStack {
                    Button {
                        _ = navigationStack.pop()
                    } label: {
                        Image("back")
                            .resizable()
                            .frame(width: 25, height: 25)
                            .padding(.leading)
                    }
                    Spacer()
                }
                .padding(.top, 20)
                    
                Spacer()
            }
            VStack {
                Button(action: {
                        
                }) {
                    Text("Third Screen")
                        .font(.largeTitle)
                        .bold()
                }
            }
        }
    }
}

#Preview {
    ThirdScreen()
}

Passing Parameters Between Screens

enum AppRoute: Hashable, Codable {
    case first
    case second(userName: String)
    case third(message: String)
}

Modified Router Logic

@main
struct RoutingInSwiftUIApp: App {
    @StateObject private var navigationStack = Router()

    var body: some Scene {
        WindowGroup {
            NavigationStack {
                VStack {
                    switch navigationStack.currentScreen() {
                    case .first:
                        FirstScreen()
                    case .second((let userName):
                        SecondScreen(userName: userName)
                    case .third:
                        ThirdScreen(message: message)
                    }
                }
            }
            .environmentObject(navigationStack)
        }
    }
}

Passing Data into Screen

SecondScreen

struct SecondScreen: View {
    @EnvironmentObject var navigationStack: Router
    var userName: String

    var body: some View {
        ZStack {
            Color.orange.ignoresSafeArea()
            VStack {
                Text("Welcome, \(userName)")
                    .font(.largeTitle)
                Button("Go to Third Screen") {
                    navigationStack.push(.third(message: "Hello from Second Screen"))
                }
            }
        }
    }
}

ThirdScreen

struct ThirdScreen: View {
    @EnvironmentObject var navigationStack: Router
    var message: String

    var body: some View {
        ZStack {
            Color.green.ignoresSafeArea()
            VStack {
                Text(message)
                    .font(.title)
                Button("Back to First") {
                    navigationStack.popTo(.first)
                }
            }
        }
    }
}


SwiftUI Routing: Pop to Any Screen

Simplified Routing System

func popTo(to target: AppRoute) {
        guard !stack.isEmpty else { return }
        while let last = stack.last, last != target {
            stack.removeLast()
        }
    }

FifthScreen with popTo Implementation

import SwiftUI

struct FifthScreen: View {
    @EnvironmentObject var navigationStack: Router
    var body: some View {
        ZStack {
            Color.secondary
                .ignoresSafeArea()
            
            VStack {
                Button(action: {
                    navigationStack.popTo(to: .first)
                }) {
                    Text("Go back to 1 screen")
                        .font(.title)
                        .bold()
                }
                Spacer()
                Button(action: {
                    navigationStack.popTo(to: .second)
                }) {
                    Text("Go back to 2 screen")
                        .font(.title)
                        .bold()
                }
                Spacer()
                Button(action: {
                    navigationStack.popTo(to: .third)
                }) {
                    Text("Go back to 3 screen")
                        .font(.title)
                        .bold()
                }
                Spacer()
                Button(action: {
                    navigationStack.popTo(to: .fourth)
                }) {
                    Text("Go back to 4 screen")
                        .font(.title)
                        .bold()
                }
            }
            .padding(.vertical, 150)
            Spacer()
        }
    }
}

#Preview {
    FifthScreen()
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *