Skip to content

Commit cc571b3

Browse files
authored
Merge pull request #6 from DKVekariya/divyesh/feature/add-stack-persistence
Supported Persistence Route
2 parents 2546334 + 591a0c8 commit cc571b3

6 files changed

Lines changed: 292 additions & 5 deletions

File tree

‎Examples/NavPilotDemo/NavPilotDemo/ContentView.swift‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ struct ContentView: View {
2424
E4SplitScreen()
2525
case .deepLinking:
2626
E5DeepLinking()
27+
case .statePersistence:
28+
E6StatePersistence()
2729
}
2830
}
2931
.sheet(isPresented: $showSheet) {
@@ -33,16 +35,14 @@ struct ContentView: View {
3335
self.navType = nav
3436
self.showSheet.toggle()
3537
}) {
36-
HStack {
37-
Text(nav.title)
38-
}
38+
Text(nav.title)
3939
.frame(maxWidth: .infinity)
4040
.padding(10)
4141
}
4242
}
4343
}
4444
.frame(maxWidth: .infinity, alignment: .center)
45-
.presentationDetents([.height(250)])
45+
.presentationDetents([.height(300)])
4646
}
4747
}
4848
}
@@ -58,6 +58,7 @@ enum NavigationType:String, Hashable, CaseIterable {
5858
case closerPass
5959
case splitScreen
6060
case deepLinking
61+
case statePersistence
6162

6263
var title: String {
6364
switch self {
@@ -66,6 +67,7 @@ enum NavigationType:String, Hashable, CaseIterable {
6667
case .closerPass: return "Closure Pass"
6768
case .splitScreen: return "Split Screen"
6869
case .deepLinking: return "Deep Linking"
70+
case .statePersistence: return "State Persistence"
6971
}
7072
}
7173
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
//
2+
// E6StatePersistence.swift
3+
// NavPilotDemo
4+
//
5+
// Created by DK on 14/05/26.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
import NavPilot
11+
12+
struct E6Product: Codable, Hashable, Equatable {
13+
let id: Int
14+
let name: String
15+
}
16+
17+
enum E6Route: Codable, Hashable {
18+
case home
19+
case product(E6Product)
20+
case checkout(items: [String])
21+
}
22+
23+
struct E6StatePersistence: View {
24+
@StateObject private var pilot = NavPilot(
25+
initial: E6Route.home,
26+
debug: true,
27+
// Set `persistState` to false to disable stack restoration.
28+
// Enable it only when the route carries enough Codable data to recreate the screen
29+
// and its view model after relaunch.
30+
persistState: true,
31+
persistenceKey: "NavPilotDemo.E6StatePersistence"
32+
)
33+
34+
var body: some View {
35+
NavPilotHost(pilot) { route in
36+
switch route {
37+
case .home:
38+
E6HomeView()
39+
case .product(let product):
40+
E6ProductView(product: product)
41+
case .checkout(let items):
42+
E6CheckoutView(items: items)
43+
}
44+
}
45+
}
46+
}
47+
48+
struct E6HomeView: View {
49+
@EnvironmentObject var pilot: NavPilot<E6Route>
50+
51+
let products = [
52+
E6Product(id: 1, name: "Keyboard"),
53+
E6Product(id: 2, name: "Monitor"),
54+
E6Product(id: 3, name: "Mouse")
55+
]
56+
57+
var body: some View {
58+
ScrollView {
59+
VStack(alignment: .leading, spacing: 16) {
60+
Text("State Persistence")
61+
.font(.largeTitle.bold())
62+
63+
Text("This demo saves the stack automatically. Push a few screens, quit the app, then reopen it to see the stack restored.")
64+
.foregroundStyle(.secondary)
65+
66+
Text("Use persistence only when the route contains enough Codable data to rebuild the screen. View models and injected services are created again when the app restores the route stack.")
67+
.font(.footnote)
68+
.foregroundStyle(.secondary)
69+
70+
VStack(alignment: .leading, spacing: 12) {
71+
ForEach(products, id: \.id) { product in
72+
Button {
73+
pilot.push(.product(product))
74+
} label: {
75+
Label(product.name, systemImage: "chevron.right")
76+
}
77+
}
78+
}
79+
.padding()
80+
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
81+
82+
Button("Add Checkout") {
83+
pilot.push(.checkout(items: ["Keyboard", "Mouse"]))
84+
}
85+
86+
Button("Pop To Root") {
87+
pilot.popToRoot()
88+
}
89+
90+
Button(role: .destructive) {
91+
UserDefaults.standard.removeObject(forKey: "NavPilotDemo.E6StatePersistence")
92+
pilot.popToRoot()
93+
} label: {
94+
Text("Clear Saved Stack")
95+
}
96+
97+
Text("If your screen depends on a view model, recreate that view model from the route data or from your app container. The persisted stack only stores navigation state, not live objects.")
98+
.font(.footnote)
99+
.foregroundStyle(.secondary)
100+
101+
VStack(alignment: .leading, spacing: 8) {
102+
Text("Current stack")
103+
.font(.headline)
104+
Text(String(describing: pilot.stack))
105+
.font(.footnote.monospaced())
106+
.foregroundStyle(.secondary)
107+
}
108+
.padding()
109+
.background(.secondary.opacity(0.1), in: RoundedRectangle(cornerRadius: 12))
110+
}
111+
.padding()
112+
}
113+
.navigationTitle("Home")
114+
}
115+
}
116+
117+
struct E6ProductView: View {
118+
@EnvironmentObject var pilot: NavPilot<E6Route>
119+
let product: E6Product
120+
121+
var body: some View {
122+
VStack(spacing: 20) {
123+
Text(product.name).font(.title2.bold())
124+
Text("This screen will be restored after relaunch if persistence is enabled.")
125+
.foregroundStyle(.secondary)
126+
.multilineTextAlignment(.center)
127+
128+
Button("Add Checkout") {
129+
pilot.push(.checkout(items: [product.name]))
130+
}
131+
132+
Button("Back") {
133+
pilot.pop()
134+
}
135+
}
136+
.padding()
137+
.frame(maxWidth: .infinity, maxHeight: .infinity)
138+
.navigationTitle(product.name)
139+
}
140+
}
141+
142+
struct E6CheckoutView: View {
143+
@EnvironmentObject var pilot: NavPilot<E6Route>
144+
let items: [String]
145+
146+
var body: some View {
147+
VStack(spacing: 20) {
148+
Text("Checkout").font(.title2.bold())
149+
Text(items.joined(separator: ", "))
150+
.foregroundStyle(.secondary)
151+
Text("After you relaunch the app, this screen should still be on the stack.")
152+
.multilineTextAlignment(.center)
153+
.foregroundStyle(.secondary)
154+
155+
Button("Pop To Root") {
156+
pilot.popToRoot()
157+
}
158+
159+
Button("Back") {
160+
pilot.pop()
161+
}
162+
}
163+
.padding()
164+
.frame(maxWidth: .infinity, maxHeight: .infinity)
165+
.navigationTitle("Checkout")
166+
}
167+
}

‎README.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ A modern, type-safe navigation library built entirely on SwiftUI's `NavigationSt
3737
### 🔗 Deep Linking
3838
- **URL round-trip** — generate and restore stack state for `Codable` routes using a lightweight URL format
3939

40+
### 💾 State Persistence
41+
- **Opt-in restore** — pass `persistState: true` for `Codable` routes to save and reload the stack automatically
42+
4043
### 🌿 Environment Injection
4144
- **`@EnvironmentObject`** — every child view receives the pilot automatically
4245
- **No prop drilling** — navigate from anywhere in the view hierarchy
@@ -134,7 +137,7 @@ enum AppRoute: Hashable {
134137
```swift
135138
@main
136139
struct MyApp: App {
137-
@StateObject var pilot = NavPilot(initial: AppRoute.home)
140+
@StateObject var pilot = NavPilot(initial: AppRoute.home, persistState: true)
138141

139142
var body: some Scene {
140143
WindowGroup {
@@ -214,6 +217,14 @@ let url = pilot.deepLinkURL()
214217
pilot.handleDeepLink(url)
215218
```
216219

220+
### State Persistence
221+
222+
When you want the stack to survive app relaunches, opt in with `persistState: true`:
223+
224+
```swift
225+
@StateObject var pilot = NavPilot(initial: AppRoute.home, persistState: true)
226+
```
227+
217228
## 🏗️ Architecture Highlights
218229

219230
### State Management
@@ -248,6 +259,7 @@ pilot.handleDeepLink(url)
248259
| `replaceCurrent(with:)` | Swap only the top-most route |
249260
| `deepLinkURL()` | Generate a URL that represents the current stack |
250261
| `handleDeepLink(_:)` | Restore the stack from a deep-link URL |
262+
| `persistState` | Opt-in initializer behavior for Codable routes that stores the stack |
251263

252264
### Properties
253265

@@ -273,6 +285,7 @@ private let activeExample: Int = 1
273285
| 3 | Callback Route | Closure passed as a route parameter |
274286
| 4 | Split Screen | Two independent pilots in a vertical split layout |
275287
| 5 | Deep Linking | Generate and restore a stack from a URL |
288+
| 6 | State Persistence | Save and restore stack across app relaunch |
276289

277290
## 🐛 Known Limitations
278291

0 commit comments

Comments
 (0)