This project demonstrates how to implement a custom navigation system using NavigationStack
and programmatic navigation in SwiftUI. It allows users to navigate between multiple views while maintaining state and dynamic content.
This example project is a SwiftUI app with a custom navigation flow. The app demonstrates how to use a NavigationStack
to navigate between views and how to use bindings and enums to manage navigation state.
- iOS 16.0+
- Xcode 14+
- Swift 5.7+
Clone the repository to your local machine:
git clone https://github.com/theranjithmenon/swiftui-navigation.git
cd swiftui-navigation
Open the project in Xcode and run it on a simulator or a physical device.
The project consists of the following files:
navigationApp.swift
: The main entry point of the app where the navigation stack and app state are defined.ContentView.swift
: Contains the main views (FirstView
,SecondView
,ThirdView
,FourthView
), which represent different screens in the app.NavigationRoutes.swift
: Defines the navigation routes and the function to get the destination view based on the route.
The app uses two primary pieces of state:
-
Path (
@State private var path: [NavigationRoutes] = []
): This is an array that represents the navigation stack's current state. Each element in the array is a route (of typeNavigationRoutes
) that represents a specific view in the navigation hierarchy. -
AppState (
@State private var appState: AppState = .firstView
): This enum tracks the current state of the app, primarily used to manage root-level navigation betweenFirstView
andThirdView
.
The app uses a combination of SwiftUI's NavigationStack
and programmatic navigation to manage view transitions:
- NavigationStack: A container view that manages the navigation stack, allowing users to navigate between views in a stack-based manner.
- NavigationRoutes Enum: Defines different possible routes in the app. It conforms to
Hashable
, which is required for it to be used with the navigation system.
enum NavigationRoutes: Hashable {
case firstView
case secondView(data: String)
case thirdView(data: String)
case fourthView(data: String)
}
getDestination Function: A helper function that returns the appropriate view based on the current route.
func getDestination(for route: NavigationRoutes, path: Binding<[NavigationRoutes]>, appState: Binding<AppState>) -> AnyView {
switch route {
case .firstView:
return AnyView(FirstView(path: path))
case .secondView(let data):
return AnyView(SecondView(path: path, appState: appState, data: data))
case .thirdView(let data):
return AnyView(ThirdView(path: path, data: data))
case .fourthView(let data):
return AnyView(FourthView(path: path, data: data))
}
}
- FirstView: The initial view where the user can enter text and navigate to
SecondView
with the entered text data.
struct FirstView: View {
@Binding var path: [NavigationRoutes]
@State private var demoText: String = ""
var body: some View {
VStack {
TextField("Text", text: $demoText)
.textFieldStyle(.roundedBorder)
.autocorrectionDisabled()
.padding()
Button("Go to second page") {
path.append(NavigationRoutes.secondView(data: demoText))
}
}
.navigationTitle("First Page")
}
}
- SecondView: Displays the text passed from
FirstView
and provides options to navigate toThirdView
or setThirdView
as the new root.
struct SecondView: View {
@Binding var path: [NavigationRoutes]
@Binding var appState: AppState
var data: String
var body: some View {
VStack {
Text(data)
Button("Go to third page") {
path.append(.thirdView(data: data))
}
Button("Make third page root") {
path.removeLast(path.count)
appState = .thirdView
}
}
.navigationTitle("Second Page")
}
}
- ThirdView: Displays data and provides a button to navigate to
FourthView
.
struct ThirdView: View {
@Binding var path: [NavigationRoutes]
var data: String
var body: some View {
VStack {
Text(data)
Button("Go to fourth page") {
path.append(.fourthView(data: data))
}
}
.navigationTitle("Third Page")
}
}
- FourthView: Displays data and provides a button to reset the navigation stack to the root.
struct FourthView: View {
@Binding var path: [NavigationRoutes]
var data: String
var body: some View {
VStack {
Text(data)
Button(action: {
path.removeLast(path.count)
}) {
Text("Go to root")
}
}
.navigationTitle("Fourth Page")
}
}