@kata-sh/cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +56 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +95 -0
  9. package/dist/resource-loader.d.ts +18 -0
  10. package/dist/resource-loader.js +50 -0
  11. package/dist/wizard.d.ts +15 -0
  12. package/dist/wizard.js +159 -0
  13. package/package.json +50 -21
  14. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  15. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  16. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  17. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  18. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  19. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  20. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  21. package/pkg/package.json +8 -0
  22. package/scripts/postinstall.js +45 -0
  23. package/src/resources/AGENTS.md +108 -0
  24. package/src/resources/KATA-WORKFLOW.md +661 -0
  25. package/src/resources/agents/researcher.md +29 -0
  26. package/src/resources/agents/scout.md +56 -0
  27. package/src/resources/agents/worker.md +31 -0
  28. package/src/resources/extensions/ask-user-questions.ts +200 -0
  29. package/src/resources/extensions/bg-shell/index.ts +2758 -0
  30. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  31. package/src/resources/extensions/browser-tools/core.js +1057 -0
  32. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  33. package/src/resources/extensions/browser-tools/package.json +20 -0
  34. package/src/resources/extensions/context7/index.ts +428 -0
  35. package/src/resources/extensions/context7/package.json +11 -0
  36. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  37. package/src/resources/extensions/github/formatters.ts +207 -0
  38. package/src/resources/extensions/github/gh-api.ts +537 -0
  39. package/src/resources/extensions/github/index.ts +778 -0
  40. package/src/resources/extensions/kata/activity-log.ts +88 -0
  41. package/src/resources/extensions/kata/auto.ts +2786 -0
  42. package/src/resources/extensions/kata/commands.ts +355 -0
  43. package/src/resources/extensions/kata/crash-recovery.ts +85 -0
  44. package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
  45. package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
  46. package/src/resources/extensions/kata/doctor.ts +683 -0
  47. package/src/resources/extensions/kata/files.ts +730 -0
  48. package/src/resources/extensions/kata/gitignore.ts +165 -0
  49. package/src/resources/extensions/kata/guided-flow.ts +976 -0
  50. package/src/resources/extensions/kata/index.ts +556 -0
  51. package/src/resources/extensions/kata/metrics.ts +397 -0
  52. package/src/resources/extensions/kata/observability-validator.ts +408 -0
  53. package/src/resources/extensions/kata/package.json +11 -0
  54. package/src/resources/extensions/kata/paths.ts +346 -0
  55. package/src/resources/extensions/kata/preferences.ts +695 -0
  56. package/src/resources/extensions/kata/prompt-loader.ts +50 -0
  57. package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
  58. package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
  59. package/src/resources/extensions/kata/prompts/discuss.md +151 -0
  60. package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
  61. package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
  62. package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
  63. package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
  64. package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
  65. package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
  66. package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
  67. package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
  68. package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
  69. package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
  70. package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
  71. package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
  72. package/src/resources/extensions/kata/prompts/queue.md +85 -0
  73. package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
  74. package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
  75. package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
  76. package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
  77. package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
  78. package/src/resources/extensions/kata/prompts/system.md +341 -0
  79. package/src/resources/extensions/kata/session-forensics.ts +550 -0
  80. package/src/resources/extensions/kata/skill-discovery.ts +137 -0
  81. package/src/resources/extensions/kata/state.ts +509 -0
  82. package/src/resources/extensions/kata/templates/context.md +76 -0
  83. package/src/resources/extensions/kata/templates/decisions.md +8 -0
  84. package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
  85. package/src/resources/extensions/kata/templates/plan.md +133 -0
  86. package/src/resources/extensions/kata/templates/preferences.md +15 -0
  87. package/src/resources/extensions/kata/templates/project.md +31 -0
  88. package/src/resources/extensions/kata/templates/reassessment.md +28 -0
  89. package/src/resources/extensions/kata/templates/requirements.md +81 -0
  90. package/src/resources/extensions/kata/templates/research.md +46 -0
  91. package/src/resources/extensions/kata/templates/roadmap.md +118 -0
  92. package/src/resources/extensions/kata/templates/slice-context.md +58 -0
  93. package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
  94. package/src/resources/extensions/kata/templates/state.md +19 -0
  95. package/src/resources/extensions/kata/templates/task-plan.md +52 -0
  96. package/src/resources/extensions/kata/templates/task-summary.md +57 -0
  97. package/src/resources/extensions/kata/templates/uat.md +54 -0
  98. package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
  99. package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
  100. package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
  101. package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
  102. package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
  103. package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
  104. package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
  105. package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
  106. package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
  107. package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
  108. package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
  109. package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
  110. package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
  111. package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
  112. package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
  113. package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
  114. package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
  115. package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
  116. package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
  117. package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
  118. package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
  119. package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
  120. package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
  121. package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
  122. package/src/resources/extensions/kata/types.ts +159 -0
  123. package/src/resources/extensions/kata/unit-runtime.ts +163 -0
  124. package/src/resources/extensions/kata/workspace-index.ts +203 -0
  125. package/src/resources/extensions/kata/worktree.ts +182 -0
  126. package/src/resources/extensions/mac-tools/index.ts +852 -0
  127. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  128. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  129. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  130. package/src/resources/extensions/search-the-web/format.ts +258 -0
  131. package/src/resources/extensions/search-the-web/http.ts +238 -0
  132. package/src/resources/extensions/search-the-web/index.ts +68 -0
  133. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  134. package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
  135. package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
  136. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  137. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  138. package/src/resources/extensions/shared/interview-ui.ts +822 -0
  139. package/src/resources/extensions/shared/next-action-ui.ts +235 -0
  140. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  141. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  142. package/src/resources/extensions/shared/ui.ts +400 -0
  143. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  144. package/src/resources/extensions/slash-commands/audit.ts +92 -0
  145. package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
  146. package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
  147. package/src/resources/extensions/slash-commands/index.ts +12 -0
  148. package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
  149. package/src/resources/extensions/subagent/agents.ts +126 -0
  150. package/src/resources/extensions/subagent/index.ts +1293 -0
  151. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  152. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  153. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  154. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  155. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  156. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  157. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  158. package/src/resources/skills/swiftui/SKILL.md +208 -0
  159. package/src/resources/skills/swiftui/references/animations.md +921 -0
  160. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  161. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  162. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  163. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  164. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  165. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  166. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  167. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  168. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  169. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  170. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  171. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  172. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  173. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  174. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  175. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
  176. package/dist/commands/task.d.ts +0 -9
  177. package/dist/commands/task.d.ts.map +0 -1
  178. package/dist/commands/task.js +0 -129
  179. package/dist/commands/task.js.map +0 -1
  180. package/dist/commands/task.test.d.ts +0 -2
  181. package/dist/commands/task.test.d.ts.map +0 -1
  182. package/dist/commands/task.test.js +0 -169
  183. package/dist/commands/task.test.js.map +0 -1
  184. package/dist/e2e/task-e2e.test.d.ts +0 -2
  185. package/dist/e2e/task-e2e.test.d.ts.map +0 -1
  186. package/dist/e2e/task-e2e.test.js +0 -173
  187. package/dist/e2e/task-e2e.test.js.map +0 -1
  188. package/dist/index.d.ts +0 -3
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js +0 -93
  191. package/dist/index.js.map +0 -1
  192. package/dist/slug.d.ts +0 -2
  193. package/dist/slug.d.ts.map +0 -1
  194. package/dist/slug.js +0 -12
  195. package/dist/slug.js.map +0 -1
  196. package/dist/slug.test.d.ts +0 -2
  197. package/dist/slug.test.d.ts.map +0 -1
  198. package/dist/slug.test.js +0 -32
  199. package/dist/slug.test.js.map +0 -1
@@ -0,0 +1,1492 @@
1
+ <overview>
2
+ SwiftUI navigation has evolved significantly with NavigationStack (iOS 16+) replacing the deprecated NavigationView. The modern navigation model provides type-safe, programmatic control while supporting both user-driven and code-driven navigation patterns.
3
+
4
+ **Key insight:** NavigationStack with NavigationPath provides a stack-based navigation system where you can programmatically manipulate the navigation hierarchy while SwiftUI keeps the UI in sync automatically.
5
+
6
+ **Read this file when:** Building multi-screen apps, implementing deep linking, managing programmatic navigation, presenting sheets and modals, or setting up tab-based navigation.
7
+
8
+ **Related files:**
9
+ - architecture.md - Coordinator pattern for complex navigation flows
10
+ - state-management.md - Managing navigation state with @Observable
11
+ - platform-integration.md - Platform-specific navigation differences (iOS vs macOS)
12
+ </overview>
13
+
14
+ <navigation_stack>
15
+ ## NavigationStack
16
+
17
+ NavigationStack manages a stack of views with type-safe routing. It replaces the deprecated NavigationView and provides better programmatic control.
18
+
19
+ **Basic usage with NavigationLink:**
20
+ ```swift
21
+ struct ContentView: View {
22
+ var body: some View {
23
+ NavigationStack {
24
+ List {
25
+ NavigationLink("Details", value: "details")
26
+ NavigationLink("Settings", value: "settings")
27
+ }
28
+ .navigationTitle("Home")
29
+ .navigationDestination(for: String.self) { value in
30
+ Text("Showing: \(value)")
31
+ }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ **With NavigationPath for programmatic navigation:**
38
+ ```swift
39
+ struct ContentView: View {
40
+ @State private var path = NavigationPath()
41
+
42
+ var body: some View {
43
+ NavigationStack(path: $path) {
44
+ VStack {
45
+ Button("Go to Details") {
46
+ path.append("details")
47
+ }
48
+
49
+ Button("Go Deep (3 levels)") {
50
+ path.append("level1")
51
+ path.append("level2")
52
+ path.append("level3")
53
+ }
54
+ }
55
+ .navigationTitle("Home")
56
+ .navigationDestination(for: String.self) { value in
57
+ DetailView(value: value, path: $path)
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ struct DetailView: View {
64
+ let value: String
65
+ @Binding var path: NavigationPath
66
+
67
+ var body: some View {
68
+ VStack {
69
+ Text("Showing: \(value)")
70
+
71
+ Button("Pop to Root") {
72
+ path = NavigationPath()
73
+ }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ **navigationDestination modifier:**
80
+
81
+ The navigationDestination modifier enables type-based routing. You can register multiple destination handlers for different data types:
82
+
83
+ ```swift
84
+ struct MultiTypeNavigation: View {
85
+ @State private var path = NavigationPath()
86
+
87
+ var body: some View {
88
+ NavigationStack(path: $path) {
89
+ List {
90
+ Button("Show User") {
91
+ path.append(User(id: 1, name: "Alice"))
92
+ }
93
+
94
+ Button("Show Product") {
95
+ path.append(Product(id: 100, title: "iPhone"))
96
+ }
97
+ }
98
+ .navigationDestination(for: User.self) { user in
99
+ UserDetailView(user: user)
100
+ }
101
+ .navigationDestination(for: Product.self) { product in
102
+ ProductDetailView(product: product)
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ struct User: Hashable, Codable {
109
+ let id: Int
110
+ let name: String
111
+ }
112
+
113
+ struct Product: Hashable, Codable {
114
+ let id: Int
115
+ let title: String
116
+ }
117
+ ```
118
+
119
+ **Key rules:**
120
+ - Place navigationDestination inside NavigationStack, not on child views
121
+ - Don't place navigationDestination on lazy containers (List, ScrollView, LazyVStack)
122
+ - Top-level navigationDestination always overrides lower ones for the same type
123
+ - Each destination type must be Hashable
124
+
125
+ **Navigation state in @Observable:**
126
+ ```swift
127
+ import Observation
128
+
129
+ @Observable
130
+ class NavigationManager {
131
+ var path = NavigationPath()
132
+
133
+ func push(_ destination: Destination) {
134
+ path.append(destination)
135
+ }
136
+
137
+ func pop() {
138
+ guard !path.isEmpty else { return }
139
+ path.removeLast()
140
+ }
141
+
142
+ func popToRoot() {
143
+ path = NavigationPath()
144
+ }
145
+ }
146
+
147
+ enum Destination: Hashable {
148
+ case detail(id: Int)
149
+ case settings
150
+ case profile
151
+ }
152
+
153
+ struct AppView: View {
154
+ @State private var navigation = NavigationManager()
155
+
156
+ var body: some View {
157
+ @Bindable var nav = navigation
158
+
159
+ NavigationStack(path: $nav.path) {
160
+ List {
161
+ Button("Details") {
162
+ navigation.push(.detail(id: 1))
163
+ }
164
+
165
+ Button("Settings") {
166
+ navigation.push(.settings)
167
+ }
168
+ }
169
+ .navigationDestination(for: Destination.self) { destination in
170
+ switch destination {
171
+ case .detail(let id):
172
+ DetailView(id: id, navigation: navigation)
173
+ case .settings:
174
+ SettingsView(navigation: navigation)
175
+ case .profile:
176
+ ProfileView(navigation: navigation)
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+ </navigation_stack>
184
+
185
+ <programmatic_navigation>
186
+ ## Programmatic Navigation
187
+
188
+ NavigationPath provides programmatic control over the navigation stack without requiring NavigationLink user interaction.
189
+
190
+ **Push to path:**
191
+ ```swift
192
+ // Push single destination
193
+ path.append(DetailDestination.item(id: 123))
194
+
195
+ // Push multiple levels at once
196
+ path.append(contentsOf: [screen1, screen2, screen3])
197
+ ```
198
+
199
+ **Pop operations:**
200
+ ```swift
201
+ // Pop one level
202
+ path.removeLast()
203
+
204
+ // Pop multiple levels
205
+ path.removeLast(2)
206
+
207
+ // Pop to root (clear entire stack)
208
+ path = NavigationPath()
209
+
210
+ // Conditional pop
211
+ if path.count > 0 {
212
+ path.removeLast()
213
+ }
214
+ ```
215
+
216
+ **Deep navigation example:**
217
+ ```swift
218
+ @Observable
219
+ class Router {
220
+ var path = NavigationPath()
221
+
222
+ func navigateToUserPosts(userId: Int, postId: Int) {
223
+ // Navigate through multiple screens
224
+ path.append(Route.userDetail(userId))
225
+ path.append(Route.userPosts(userId))
226
+ path.append(Route.postDetail(postId))
227
+ }
228
+
229
+ func popToUserDetail() {
230
+ // Remove specific number of levels
231
+ if path.count >= 2 {
232
+ path.removeLast(2)
233
+ }
234
+ }
235
+ }
236
+
237
+ enum Route: Hashable {
238
+ case userDetail(Int)
239
+ case userPosts(Int)
240
+ case postDetail(Int)
241
+ }
242
+ ```
243
+
244
+ **NavigationPath count and inspection:**
245
+ ```swift
246
+ struct NavigationDebugView: View {
247
+ @State private var path = NavigationPath()
248
+
249
+ var body: some View {
250
+ NavigationStack(path: $path) {
251
+ VStack {
252
+ Text("Stack depth: \(path.count)")
253
+
254
+ Button("Push") {
255
+ path.append("Level \(path.count + 1)")
256
+ }
257
+
258
+ Button("Pop") {
259
+ if !path.isEmpty {
260
+ path.removeLast()
261
+ }
262
+ }
263
+
264
+ Button("Pop to Root") {
265
+ path = NavigationPath()
266
+ }
267
+ }
268
+ .navigationDestination(for: String.self) { value in
269
+ Text(value)
270
+ }
271
+ }
272
+ }
273
+ }
274
+ ```
275
+ </programmatic_navigation>
276
+
277
+ <sheets_and_covers>
278
+ ## Sheet and FullScreenCover
279
+
280
+ Sheets present modal content on top of the current view. They are not part of the NavigationStack hierarchy.
281
+
282
+ **Basic sheet with boolean:**
283
+ ```swift
284
+ struct SheetExample: View {
285
+ @State private var showingSheet = false
286
+
287
+ var body: some View {
288
+ Button("Show Sheet") {
289
+ showingSheet = true
290
+ }
291
+ .sheet(isPresented: $showingSheet) {
292
+ SheetContentView()
293
+ }
294
+ }
295
+ }
296
+
297
+ struct SheetContentView: View {
298
+ @Environment(\.dismiss) private var dismiss
299
+
300
+ var body: some View {
301
+ NavigationStack {
302
+ VStack {
303
+ Text("Sheet Content")
304
+ Button("Close") {
305
+ dismiss()
306
+ }
307
+ }
308
+ .navigationTitle("Modal")
309
+ .toolbar {
310
+ ToolbarItem(placement: .cancellationAction) {
311
+ Button("Cancel") {
312
+ dismiss()
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ ```
320
+
321
+ **Item-based presentation (type-safe, recommended):**
322
+ ```swift
323
+ struct ItemSheet: View {
324
+ @State private var selectedUser: User?
325
+
326
+ var body: some View {
327
+ List(users) { user in
328
+ Button(user.name) {
329
+ selectedUser = user
330
+ }
331
+ }
332
+ .sheet(item: $selectedUser) { user in
333
+ UserDetailSheet(user: user)
334
+ }
335
+ }
336
+ }
337
+
338
+ struct User: Identifiable {
339
+ let id: UUID
340
+ let name: String
341
+ }
342
+
343
+ struct UserDetailSheet: View {
344
+ let user: User
345
+ @Environment(\.dismiss) private var dismiss
346
+
347
+ var body: some View {
348
+ NavigationStack {
349
+ VStack {
350
+ Text("User: \(user.name)")
351
+ }
352
+ .navigationTitle(user.name)
353
+ .toolbar {
354
+ ToolbarItem(placement: .confirmationAction) {
355
+ Button("Done") {
356
+ dismiss()
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ ```
364
+
365
+ **FullScreenCover:**
366
+ ```swift
367
+ struct FullScreenExample: View {
368
+ @State private var showingFullScreen = false
369
+
370
+ var body: some View {
371
+ Button("Show Full Screen") {
372
+ showingFullScreen = true
373
+ }
374
+ .fullScreenCover(isPresented: $showingFullScreen) {
375
+ FullScreenContentView()
376
+ }
377
+ }
378
+ }
379
+
380
+ struct FullScreenContentView: View {
381
+ @Environment(\.dismiss) private var dismiss
382
+
383
+ var body: some View {
384
+ NavigationStack {
385
+ VStack {
386
+ Text("Full Screen Content")
387
+ .font(.largeTitle)
388
+ }
389
+ .toolbar {
390
+ ToolbarItem(placement: .cancellationAction) {
391
+ Button("Close") {
392
+ dismiss()
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+ }
399
+ ```
400
+
401
+ **Presentation detents (iOS 16+):**
402
+ ```swift
403
+ struct DetentSheet: View {
404
+ @State private var showingSheet = false
405
+ @State private var selectedDetent: PresentationDetent = .medium
406
+
407
+ var body: some View {
408
+ Button("Show Customizable Sheet") {
409
+ showingSheet = true
410
+ }
411
+ .sheet(isPresented: $showingSheet) {
412
+ SheetWithDetents(selectedDetent: $selectedDetent)
413
+ .presentationDetents(
414
+ [.medium, .large, .fraction(0.25), .height(200)],
415
+ selection: $selectedDetent
416
+ )
417
+ .presentationDragIndicator(.visible)
418
+ .presentationBackgroundInteraction(.enabled(upThrough: .medium))
419
+ }
420
+ }
421
+ }
422
+
423
+ struct SheetWithDetents: View {
424
+ @Binding var selectedDetent: PresentationDetent
425
+
426
+ var body: some View {
427
+ VStack {
428
+ Text("Drag to resize")
429
+ .font(.headline)
430
+
431
+ Text("Current detent: \(detentDescription)")
432
+ .font(.caption)
433
+ }
434
+ .padding()
435
+ }
436
+
437
+ var detentDescription: String {
438
+ if selectedDetent == .medium { return "Medium" }
439
+ if selectedDetent == .large { return "Large" }
440
+ return "Custom"
441
+ }
442
+ }
443
+ ```
444
+
445
+ **Dismiss from presented view:**
446
+ ```swift
447
+ struct DismissExample: View {
448
+ @Environment(\.dismiss) private var dismiss
449
+
450
+ var body: some View {
451
+ VStack {
452
+ Text("Modal Content")
453
+
454
+ Button("Dismiss") {
455
+ dismiss()
456
+ }
457
+ }
458
+ }
459
+ }
460
+ ```
461
+ </sheets_and_covers>
462
+
463
+ <tab_view>
464
+ ## TabView
465
+
466
+ TabView presents multiple independent navigation hierarchies. Each tab typically contains its own NavigationStack.
467
+
468
+ **Basic TabView:**
469
+ ```swift
470
+ struct TabExample: View {
471
+ var body: some View {
472
+ TabView {
473
+ HomeView()
474
+ .tabItem {
475
+ Label("Home", systemImage: "house")
476
+ }
477
+
478
+ SearchView()
479
+ .tabItem {
480
+ Label("Search", systemImage: "magnifyingglass")
481
+ }
482
+
483
+ ProfileView()
484
+ .tabItem {
485
+ Label("Profile", systemImage: "person")
486
+ }
487
+ }
488
+ }
489
+ }
490
+
491
+ struct HomeView: View {
492
+ var body: some View {
493
+ NavigationStack {
494
+ List {
495
+ Text("Home Content")
496
+ }
497
+ .navigationTitle("Home")
498
+ }
499
+ }
500
+ }
501
+ ```
502
+
503
+ **Programmatic tab selection:**
504
+ ```swift
505
+ struct ProgrammaticTabView: View {
506
+ @State private var selectedTab = Tab.home
507
+
508
+ var body: some View {
509
+ TabView(selection: $selectedTab) {
510
+ HomeView()
511
+ .tabItem {
512
+ Label("Home", systemImage: "house")
513
+ }
514
+ .tag(Tab.home)
515
+
516
+ SearchView()
517
+ .tabItem {
518
+ Label("Search", systemImage: "magnifyingglass")
519
+ }
520
+ .tag(Tab.search)
521
+
522
+ ProfileView()
523
+ .tabItem {
524
+ Label("Profile", systemImage: "person")
525
+ }
526
+ .tag(Tab.profile)
527
+ }
528
+ .onChange(of: selectedTab) { oldValue, newValue in
529
+ print("Tab changed from \(oldValue) to \(newValue)")
530
+ }
531
+ }
532
+ }
533
+
534
+ enum Tab {
535
+ case home
536
+ case search
537
+ case profile
538
+ }
539
+ ```
540
+
541
+ **Each tab with independent NavigationStack:**
542
+ ```swift
543
+ struct IndependentTabStacks: View {
544
+ @State private var homeNavPath = NavigationPath()
545
+ @State private var searchNavPath = NavigationPath()
546
+
547
+ var body: some View {
548
+ TabView {
549
+ NavigationStack(path: $homeNavPath) {
550
+ HomeRootView()
551
+ .navigationDestination(for: HomeDestination.self) { destination in
552
+ // Home-specific destinations
553
+ Text("Home destination")
554
+ }
555
+ }
556
+ .tabItem {
557
+ Label("Home", systemImage: "house")
558
+ }
559
+
560
+ NavigationStack(path: $searchNavPath) {
561
+ SearchRootView()
562
+ .navigationDestination(for: SearchDestination.self) { destination in
563
+ // Search-specific destinations
564
+ Text("Search destination")
565
+ }
566
+ }
567
+ .tabItem {
568
+ Label("Search", systemImage: "magnifyingglass")
569
+ }
570
+ }
571
+ }
572
+ }
573
+
574
+ enum HomeDestination: Hashable {
575
+ case detail(Int)
576
+ }
577
+
578
+ enum SearchDestination: Hashable {
579
+ case results(String)
580
+ }
581
+ ```
582
+
583
+ **iOS 18 Tab API:**
584
+ ```swift
585
+ // iOS 18 introduces new Tab syntax with better customization
586
+ @available(iOS 18.0, *)
587
+ struct ModernTabView: View {
588
+ @State private var selectedTab: TabIdentifier = .home
589
+
590
+ var body: some View {
591
+ TabView(selection: $selectedTab) {
592
+ Tab("Home", systemImage: "house", value: .home) {
593
+ NavigationStack {
594
+ HomeView()
595
+ }
596
+ }
597
+
598
+ Tab("Search", systemImage: "magnifyingglass", value: .search) {
599
+ NavigationStack {
600
+ SearchView()
601
+ }
602
+ }
603
+ .badge(5) // Badge support
604
+
605
+ Tab("Profile", systemImage: "person", value: .profile) {
606
+ NavigationStack {
607
+ ProfileView()
608
+ }
609
+ }
610
+ .customizationID("profile") // Enables tab customization
611
+ }
612
+ .tabViewStyle(.sidebarAdaptable) // Sidebar on iPad
613
+ }
614
+ }
615
+
616
+ enum TabIdentifier: Hashable {
617
+ case home
618
+ case search
619
+ case profile
620
+ }
621
+ ```
622
+
623
+ **Tab badges:**
624
+ ```swift
625
+ struct BadgedTabs: View {
626
+ @State private var unreadCount = 3
627
+
628
+ var body: some View {
629
+ TabView {
630
+ HomeView()
631
+ .tabItem {
632
+ Label("Home", systemImage: "house")
633
+ }
634
+
635
+ MessagesView()
636
+ .tabItem {
637
+ Label("Messages", systemImage: "message")
638
+ }
639
+ .badge(unreadCount)
640
+
641
+ ProfileView()
642
+ .tabItem {
643
+ Label("Profile", systemImage: "person")
644
+ }
645
+ }
646
+ }
647
+ }
648
+ ```
649
+
650
+ **Platform differences:**
651
+ - **iOS:** Bottom tabs with up to 5 visible items (more creates "More" tab)
652
+ - **macOS:** Top tabs or sidebar style
653
+ - **iPadOS:** Can transform to sidebar with .tabViewStyle(.sidebarAdaptable)
654
+ - **watchOS:** PageTabViewStyle (swipeable pages)
655
+ </tab_view>
656
+
657
+ <deep_linking>
658
+ ## Deep Linking
659
+
660
+ Deep linking enables opening your app to specific screens via URLs, supporting both custom URL schemes and Universal Links.
661
+
662
+ **URL handling with onOpenURL:**
663
+ ```swift
664
+ @main
665
+ struct MyApp: App {
666
+ @State private var router = Router()
667
+
668
+ var body: some Scene {
669
+ WindowGroup {
670
+ ContentView()
671
+ .environment(router)
672
+ .onOpenURL { url in
673
+ handleDeepLink(url)
674
+ }
675
+ }
676
+ }
677
+
678
+ private func handleDeepLink(_ url: URL) {
679
+ router.handleDeepLink(url)
680
+ }
681
+ }
682
+
683
+ @Observable
684
+ class Router {
685
+ var path = NavigationPath()
686
+
687
+ func handleDeepLink(_ url: URL) {
688
+ // Parse URL and update navigation
689
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
690
+ return
691
+ }
692
+
693
+ // myapp://user/123
694
+ if components.scheme == "myapp",
695
+ components.host == "user",
696
+ let userId = components.path.split(separator: "/").first,
697
+ let id = Int(userId) {
698
+ navigateToUser(id: id)
699
+ }
700
+
701
+ // myapp://product/456/reviews
702
+ if components.scheme == "myapp",
703
+ components.host == "product" {
704
+ let pathComponents = components.path.split(separator: "/")
705
+ if let productId = pathComponents.first,
706
+ let id = Int(productId) {
707
+ navigateToProduct(id: id, showReviews: pathComponents.contains("reviews"))
708
+ }
709
+ }
710
+ }
711
+
712
+ func navigateToUser(id: Int) {
713
+ path = NavigationPath() // Reset to root
714
+ path.append(Route.userDetail(id))
715
+ }
716
+
717
+ func navigateToProduct(id: Int, showReviews: Bool) {
718
+ path = NavigationPath()
719
+ path.append(Route.productDetail(id))
720
+ if showReviews {
721
+ path.append(Route.productReviews(id))
722
+ }
723
+ }
724
+ }
725
+
726
+ enum Route: Hashable {
727
+ case userDetail(Int)
728
+ case productDetail(Int)
729
+ case productReviews(Int)
730
+ }
731
+ ```
732
+
733
+ **Parsing URLs into navigation state:**
734
+ ```swift
735
+ @Observable
736
+ class DeepLinkRouter {
737
+ var path = NavigationPath()
738
+ var selectedTab: AppTab = .home
739
+
740
+ func handleDeepLink(_ url: URL) {
741
+ // Parse URL: myapp://tab/search?query=SwiftUI&filter=recent
742
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
743
+ return
744
+ }
745
+
746
+ // Handle tab switching
747
+ if components.host == "tab",
748
+ let tabName = components.path.split(separator: "/").first {
749
+ switchTab(String(tabName))
750
+ }
751
+
752
+ // Handle query parameters
753
+ let queryItems = components.queryItems ?? []
754
+ if let query = queryItems.first(where: { $0.name == "query" })?.value {
755
+ navigateToSearch(query: query)
756
+ }
757
+ }
758
+
759
+ private func switchTab(_ tab: String) {
760
+ switch tab {
761
+ case "home": selectedTab = .home
762
+ case "search": selectedTab = .search
763
+ case "profile": selectedTab = .profile
764
+ default: break
765
+ }
766
+ }
767
+
768
+ private func navigateToSearch(query: String) {
769
+ selectedTab = .search
770
+ path = NavigationPath()
771
+ path.append(SearchRoute.results(query))
772
+ }
773
+ }
774
+
775
+ enum AppTab {
776
+ case home
777
+ case search
778
+ case profile
779
+ }
780
+
781
+ enum SearchRoute: Hashable {
782
+ case results(String)
783
+ }
784
+ ```
785
+
786
+ **Universal Links setup:**
787
+
788
+ 1. **Associated Domains entitlement:** Add in Xcode project capabilities
789
+ - `applinks:example.com`
790
+
791
+ 2. **apple-app-site-association file:** Host at `https://example.com/.well-known/apple-app-site-association`
792
+ ```json
793
+ {
794
+ "applinks": {
795
+ "apps": [],
796
+ "details": [
797
+ {
798
+ "appID": "TEAM_ID.com.example.myapp",
799
+ "paths": [
800
+ "/user/*",
801
+ "/product/*"
802
+ ]
803
+ }
804
+ ]
805
+ }
806
+ }
807
+ ```
808
+
809
+ 3. **Handle in app:**
810
+ ```swift
811
+ @main
812
+ struct MyApp: App {
813
+ @State private var router = Router()
814
+
815
+ var body: some Scene {
816
+ WindowGroup {
817
+ ContentView()
818
+ .environment(router)
819
+ .onOpenURL { url in
820
+ router.handleUniversalLink(url)
821
+ }
822
+ }
823
+ }
824
+ }
825
+ ```
826
+
827
+ **Custom URL schemes:**
828
+
829
+ 1. **Register scheme in Info.plist:**
830
+ ```xml
831
+ <key>CFBundleURLTypes</key>
832
+ <array>
833
+ <dict>
834
+ <key>CFBundleURLSchemes</key>
835
+ <array>
836
+ <string>myapp</string>
837
+ </array>
838
+ <key>CFBundleURLName</key>
839
+ <string>com.example.myapp</string>
840
+ </dict>
841
+ </array>
842
+ ```
843
+
844
+ 2. **Handle custom scheme:**
845
+ ```swift
846
+ struct ContentView: View {
847
+ @Environment(Router.self) private var router
848
+
849
+ var body: some View {
850
+ @Bindable var router = router
851
+
852
+ NavigationStack(path: $router.path) {
853
+ HomeView()
854
+ .navigationDestination(for: Route.self) { route in
855
+ destinationView(for: route)
856
+ }
857
+ }
858
+ }
859
+
860
+ @ViewBuilder
861
+ func destinationView(for route: Route) -> some View {
862
+ switch route {
863
+ case .userDetail(let id):
864
+ UserDetailView(userId: id)
865
+ case .productDetail(let id):
866
+ ProductDetailView(productId: id)
867
+ case .productReviews(let id):
868
+ ProductReviewsView(productId: id)
869
+ }
870
+ }
871
+ }
872
+ ```
873
+
874
+ **Security considerations:**
875
+ - Validate all incoming URLs
876
+ - Sanitize parameters before using them
877
+ - Don't expose sensitive functionality via deep links
878
+ - Use Universal Links over custom URL schemes for production (more secure, unique)
879
+ </deep_linking>
880
+
881
+ <coordinator_pattern>
882
+ ## Coordinator Pattern (Optional)
883
+
884
+ The Coordinator pattern centralizes navigation logic, decoupling it from views. Use when navigation becomes complex enough to justify the abstraction.
885
+
886
+ **When to use:**
887
+ - Complex navigation flows with many paths
888
+ - Testable navigation logic separated from views
889
+ - Multiple entry points to the same flow
890
+ - Deep linking with complex routing
891
+
892
+ **Implementation with @Observable:**
893
+ ```swift
894
+ import Observation
895
+
896
+ @Observable
897
+ class AppCoordinator {
898
+ var path = NavigationPath()
899
+ var sheet: Sheet?
900
+ var fullScreenCover: Cover?
901
+
902
+ // MARK: - Navigation
903
+
904
+ func push(_ destination: Destination) {
905
+ path.append(destination)
906
+ }
907
+
908
+ func pop() {
909
+ guard !path.isEmpty else { return }
910
+ path.removeLast()
911
+ }
912
+
913
+ func popToRoot() {
914
+ path = NavigationPath()
915
+ }
916
+
917
+ // MARK: - Sheets
918
+
919
+ func presentSheet(_ sheet: Sheet) {
920
+ self.sheet = sheet
921
+ }
922
+
923
+ func dismissSheet() {
924
+ self.sheet = nil
925
+ }
926
+
927
+ // MARK: - Full Screen
928
+
929
+ func presentFullScreen(_ cover: Cover) {
930
+ self.fullScreenCover = cover
931
+ }
932
+
933
+ func dismissFullScreen() {
934
+ self.fullScreenCover = nil
935
+ }
936
+
937
+ // MARK: - Deep Linking
938
+
939
+ func handleDeepLink(_ url: URL) {
940
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
941
+ return
942
+ }
943
+
944
+ if components.path.contains("/user/"),
945
+ let userId = extractId(from: components.path) {
946
+ popToRoot()
947
+ push(.userDetail(userId))
948
+ }
949
+ }
950
+
951
+ private func extractId(from path: String) -> Int? {
952
+ let components = path.split(separator: "/")
953
+ return components.last.flatMap { Int($0) }
954
+ }
955
+ }
956
+
957
+ enum Destination: Hashable {
958
+ case userDetail(Int)
959
+ case settings
960
+ case editProfile
961
+ }
962
+
963
+ enum Sheet: Identifiable {
964
+ case addUser
965
+ case filter
966
+
967
+ var id: String {
968
+ switch self {
969
+ case .addUser: return "addUser"
970
+ case .filter: return "filter"
971
+ }
972
+ }
973
+ }
974
+
975
+ enum Cover: Identifiable {
976
+ case onboarding
977
+ case camera
978
+
979
+ var id: String {
980
+ switch self {
981
+ case .onboarding: return "onboarding"
982
+ case .camera: return "camera"
983
+ }
984
+ }
985
+ }
986
+
987
+ // MARK: - Root View
988
+
989
+ struct RootView: View {
990
+ @State private var coordinator = AppCoordinator()
991
+
992
+ var body: some View {
993
+ @Bindable var coordinator = coordinator
994
+
995
+ NavigationStack(path: $coordinator.path) {
996
+ UserListView()
997
+ .navigationDestination(for: Destination.self) { destination in
998
+ destinationView(for: destination)
999
+ }
1000
+ }
1001
+ .sheet(item: $coordinator.sheet) { sheet in
1002
+ sheetView(for: sheet)
1003
+ }
1004
+ .fullScreenCover(item: $coordinator.fullScreenCover) { cover in
1005
+ coverView(for: cover)
1006
+ }
1007
+ .environment(coordinator)
1008
+ .onOpenURL { url in
1009
+ coordinator.handleDeepLink(url)
1010
+ }
1011
+ }
1012
+
1013
+ @ViewBuilder
1014
+ func destinationView(for destination: Destination) -> some View {
1015
+ switch destination {
1016
+ case .userDetail(let id):
1017
+ UserDetailView(userId: id)
1018
+ case .settings:
1019
+ SettingsView()
1020
+ case .editProfile:
1021
+ EditProfileView()
1022
+ }
1023
+ }
1024
+
1025
+ @ViewBuilder
1026
+ func sheetView(for sheet: Sheet) -> some View {
1027
+ switch sheet {
1028
+ case .addUser:
1029
+ AddUserView()
1030
+ case .filter:
1031
+ FilterView()
1032
+ }
1033
+ }
1034
+
1035
+ @ViewBuilder
1036
+ func coverView(for cover: Cover) -> some View {
1037
+ switch cover {
1038
+ case .onboarding:
1039
+ OnboardingView()
1040
+ case .camera:
1041
+ CameraView()
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ // MARK: - Views using coordinator
1047
+
1048
+ struct UserListView: View {
1049
+ @Environment(AppCoordinator.self) private var coordinator
1050
+
1051
+ var body: some View {
1052
+ List {
1053
+ ForEach(users) { user in
1054
+ Button(user.name) {
1055
+ coordinator.push(.userDetail(user.id))
1056
+ }
1057
+ }
1058
+ }
1059
+ .navigationTitle("Users")
1060
+ .toolbar {
1061
+ Button("Add") {
1062
+ coordinator.presentSheet(.addUser)
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
+ let users = [
1068
+ User(id: 1, name: "Alice"),
1069
+ User(id: 2, name: "Bob")
1070
+ ]
1071
+ }
1072
+
1073
+ struct UserDetailView: View {
1074
+ let userId: Int
1075
+ @Environment(AppCoordinator.self) private var coordinator
1076
+
1077
+ var body: some View {
1078
+ VStack {
1079
+ Text("User \(userId)")
1080
+
1081
+ Button("Edit Profile") {
1082
+ coordinator.push(.editProfile)
1083
+ }
1084
+
1085
+ Button("Pop to Root") {
1086
+ coordinator.popToRoot()
1087
+ }
1088
+ }
1089
+ .navigationTitle("User Detail")
1090
+ }
1091
+ }
1092
+ ```
1093
+
1094
+ **Trade-offs:**
1095
+ - **Pros:** Testable navigation logic, centralized flow control, easier deep linking, decoupled views
1096
+ - **Cons:** Additional abstraction layer, more code to maintain, can be overkill for simple apps
1097
+
1098
+ **When NOT to use:** Simple apps with linear navigation, apps with fewer than 10 screens, prototypes
1099
+ </coordinator_pattern>
1100
+
1101
+ <state_persistence>
1102
+ ## Navigation State Persistence
1103
+
1104
+ Enable state restoration so users return to where they left off when reopening your app.
1105
+
1106
+ **Codable NavigationPath:**
1107
+ ```swift
1108
+ struct PersistentNavigation: View {
1109
+ @State private var path = NavigationPath()
1110
+ @AppStorage("navigationPath") private var navigationPathData: Data?
1111
+
1112
+ var body: some View {
1113
+ NavigationStack(path: $path) {
1114
+ List {
1115
+ NavigationLink("Details", value: Route.details)
1116
+ NavigationLink("Settings", value: Route.settings)
1117
+ }
1118
+ .navigationTitle("Home")
1119
+ .navigationDestination(for: Route.self) { route in
1120
+ routeView(for: route)
1121
+ }
1122
+ }
1123
+ .onAppear {
1124
+ restorePath()
1125
+ }
1126
+ .onChange(of: path) { oldPath, newPath in
1127
+ savePath()
1128
+ }
1129
+ }
1130
+
1131
+ @ViewBuilder
1132
+ func routeView(for route: Route) -> some View {
1133
+ switch route {
1134
+ case .details:
1135
+ Text("Details")
1136
+ case .settings:
1137
+ Text("Settings")
1138
+ }
1139
+ }
1140
+
1141
+ func savePath() {
1142
+ guard let representation = path.codable else { return }
1143
+
1144
+ do {
1145
+ let data = try JSONEncoder().encode(representation)
1146
+ navigationPathData = data
1147
+ } catch {
1148
+ print("Failed to save path: \(error)")
1149
+ }
1150
+ }
1151
+
1152
+ func restorePath() {
1153
+ guard let data = navigationPathData else { return }
1154
+
1155
+ do {
1156
+ let representation = try JSONDecoder().decode(
1157
+ NavigationPath.CodableRepresentation.self,
1158
+ from: data
1159
+ )
1160
+ path = NavigationPath(representation)
1161
+ } catch {
1162
+ print("Failed to restore path: \(error)")
1163
+ }
1164
+ }
1165
+ }
1166
+
1167
+ enum Route: Hashable, Codable {
1168
+ case details
1169
+ case settings
1170
+ }
1171
+ ```
1172
+
1173
+ **@SceneStorage for restoration:**
1174
+ ```swift
1175
+ struct SceneStorageNavigation: View {
1176
+ @SceneStorage("navigationPath") private var pathData: Data?
1177
+ @State private var path = NavigationPath()
1178
+
1179
+ var body: some View {
1180
+ NavigationStack(path: $path) {
1181
+ List {
1182
+ NavigationLink("Item 1", value: 1)
1183
+ NavigationLink("Item 2", value: 2)
1184
+ }
1185
+ .navigationDestination(for: Int.self) { value in
1186
+ DetailView(value: value)
1187
+ }
1188
+ }
1189
+ .task {
1190
+ if let data = pathData,
1191
+ let representation = try? JSONDecoder().decode(
1192
+ NavigationPath.CodableRepresentation.self,
1193
+ from: data
1194
+ ) {
1195
+ path = NavigationPath(representation)
1196
+ }
1197
+ }
1198
+ .onChange(of: path) { _, newPath in
1199
+ if let representation = newPath.codable,
1200
+ let data = try? JSONEncoder().encode(representation) {
1201
+ pathData = data
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ ```
1207
+
1208
+ **Important notes:**
1209
+ - Only works if all types in NavigationPath are Codable
1210
+ - @SceneStorage cleared when user force-quits app
1211
+ - @AppStorage persists across launches but not recommended for large data
1212
+ - Test restoration thoroughly (background app, force quit, etc.)
1213
+ </state_persistence>
1214
+
1215
+ <decision_tree>
1216
+ ## Choosing the Right Approach
1217
+
1218
+ **Simple app with few screens:** NavigationStack with NavigationLink (user-driven navigation is sufficient)
1219
+
1220
+ **Need programmatic navigation:** NavigationStack + NavigationPath in @Observable class stored in @State
1221
+
1222
+ **Modal content (settings, forms, detail overlays):** .sheet() for dismissible modals, .fullScreenCover() for immersive content
1223
+
1224
+ **Multiple independent sections:** TabView with separate NavigationStack per tab
1225
+
1226
+ **Deep linking required:** onOpenURL + NavigationPath (parse URL and manipulate path programmatically)
1227
+
1228
+ **Complex navigation flows (10+ screens, multiple entry points):** Coordinator pattern with @Observable coordinator managing NavigationPath and sheet/cover state
1229
+
1230
+ **State restoration needed:** NavigationPath.codable with @SceneStorage or @AppStorage
1231
+
1232
+ **Platform differences matter:** Check platform in architecture.md, use NavigationSplitView for iPad/macOS multi-column layouts
1233
+ </decision_tree>
1234
+
1235
+ <anti_patterns>
1236
+ ## What NOT to Do
1237
+
1238
+ <anti_pattern name="Using NavigationView">
1239
+ **Problem:** NavigationView is deprecated in iOS 16+
1240
+
1241
+ **Why it's bad:**
1242
+ - Missing modern features (programmatic navigation, type-safe routing)
1243
+ - Deprecated API that may be removed
1244
+ - NavigationStack is more performant and flexible
1245
+
1246
+ **Instead:** Use NavigationStack
1247
+ ```swift
1248
+ // WRONG
1249
+ NavigationView {
1250
+ List { }
1251
+ }
1252
+
1253
+ // RIGHT
1254
+ NavigationStack {
1255
+ List { }
1256
+ }
1257
+ ```
1258
+ </anti_pattern>
1259
+
1260
+ <anti_pattern name="Boolean flags for navigation">
1261
+ **Problem:** Using @State var showDetail = false for each destination
1262
+
1263
+ **Why it's bad:**
1264
+ - Doesn't scale beyond 2-3 screens
1265
+ - Loses type safety (what data does the destination need?)
1266
+ - Can't programmatically navigate deep
1267
+ - No navigation history
1268
+
1269
+ **Instead:** Use navigationDestination with typed values
1270
+ ```swift
1271
+ // WRONG
1272
+ @State private var showUserDetail = false
1273
+ @State private var showSettings = false
1274
+ @State private var showProfile = false
1275
+
1276
+ // RIGHT
1277
+ @State private var path = NavigationPath()
1278
+
1279
+ NavigationStack(path: $path) {
1280
+ Button("Show User") {
1281
+ path.append(Route.userDetail(id: 1))
1282
+ }
1283
+ .navigationDestination(for: Route.self) { route in
1284
+ // Handle route
1285
+ }
1286
+ }
1287
+ ```
1288
+ </anti_pattern>
1289
+
1290
+ <anti_pattern name="Storing NavigationPath in @State at wrong level">
1291
+ **Problem:** Storing NavigationPath in child views that need to access it
1292
+
1293
+ **Why it's bad:**
1294
+ - Child views can't access parent's NavigationPath
1295
+ - Forces passing bindings through many levels
1296
+ - Breaks encapsulation
1297
+
1298
+ **Instead:** Store in @Observable, pass via @Environment
1299
+ ```swift
1300
+ // WRONG
1301
+ struct ChildView: View {
1302
+ @State private var path = NavigationPath() // Can't access parent's path
1303
+ }
1304
+
1305
+ // RIGHT
1306
+ @Observable
1307
+ class Router {
1308
+ var path = NavigationPath()
1309
+ }
1310
+
1311
+ @main
1312
+ struct App: App {
1313
+ @State private var router = Router()
1314
+
1315
+ var body: some Scene {
1316
+ WindowGroup {
1317
+ ContentView()
1318
+ .environment(router)
1319
+ }
1320
+ }
1321
+ }
1322
+
1323
+ struct ChildView: View {
1324
+ @Environment(Router.self) private var router
1325
+
1326
+ var body: some View {
1327
+ Button("Navigate") {
1328
+ router.path.append(destination)
1329
+ }
1330
+ }
1331
+ }
1332
+ ```
1333
+ </anti_pattern>
1334
+
1335
+ <anti_pattern name="Placing navigationDestination on lazy containers">
1336
+ **Problem:** Putting navigationDestination inside List, ScrollView, LazyVStack
1337
+
1338
+ **Why it's bad:**
1339
+ - Destination closures may not be called
1340
+ - Lazy loading means modifiers aren't registered
1341
+ - Apple explicitly warns against this
1342
+
1343
+ **Instead:** Place navigationDestination on NavigationStack or its immediate child
1344
+ ```swift
1345
+ // WRONG
1346
+ NavigationStack {
1347
+ List {
1348
+ ForEach(items) { item in
1349
+ NavigationLink(item.name, value: item)
1350
+ }
1351
+ .navigationDestination(for: Item.self) { item in // ❌ Inside List
1352
+ DetailView(item: item)
1353
+ }
1354
+ }
1355
+ }
1356
+
1357
+ // RIGHT
1358
+ NavigationStack {
1359
+ List {
1360
+ ForEach(items) { item in
1361
+ NavigationLink(item.name, value: item)
1362
+ }
1363
+ }
1364
+ .navigationDestination(for: Item.self) { item in // ✅ Outside List
1365
+ DetailView(item: item)
1366
+ }
1367
+ }
1368
+ ```
1369
+ </anti_pattern>
1370
+
1371
+ <anti_pattern name="Mixing sheets with NavigationStack for sequential flows">
1372
+ **Problem:** Using sheets for multi-step flows that should be pushed
1373
+
1374
+ **Why it's bad:**
1375
+ - Sheets are for modal content, not hierarchical navigation
1376
+ - Can't use back button (must dismiss)
1377
+ - Breaks user expectations
1378
+ - No navigation history
1379
+
1380
+ **Instead:** Use NavigationStack for flows, sheets for modals
1381
+ ```swift
1382
+ // WRONG - using sheets for sequential steps
1383
+ .sheet(isPresented: $showStep2) {
1384
+ Step2View()
1385
+ .sheet(isPresented: $showStep3) {
1386
+ Step3View() // Nested sheets
1387
+ }
1388
+ }
1389
+
1390
+ // RIGHT - NavigationStack for flows
1391
+ NavigationStack(path: $path) {
1392
+ Step1View()
1393
+ .navigationDestination(for: Step.self) { step in
1394
+ switch step {
1395
+ case .step2: Step2View()
1396
+ case .step3: Step3View()
1397
+ }
1398
+ }
1399
+ }
1400
+
1401
+ // RIGHT - Sheets for modals
1402
+ .sheet(isPresented: $showSettings) {
1403
+ SettingsView() // Self-contained modal
1404
+ }
1405
+ ```
1406
+ </anti_pattern>
1407
+
1408
+ <anti_pattern name="Not making navigation types Hashable">
1409
+ **Problem:** Forgetting to conform to Hashable for navigationDestination types
1410
+
1411
+ **Why it's bad:**
1412
+ - Compiler error: navigationDestination requires Hashable
1413
+ - NavigationPath can't store non-Hashable types
1414
+
1415
+ **Instead:** Always make route types Hashable (and Codable for persistence)
1416
+ ```swift
1417
+ // WRONG
1418
+ struct Route {
1419
+ let id: Int
1420
+ }
1421
+
1422
+ // RIGHT
1423
+ struct Route: Hashable {
1424
+ let id: Int
1425
+ }
1426
+
1427
+ // EVEN BETTER - also Codable for persistence
1428
+ enum Route: Hashable, Codable {
1429
+ case detail(id: Int)
1430
+ case settings
1431
+ }
1432
+ ```
1433
+ </anti_pattern>
1434
+
1435
+ <anti_pattern name="Creating separate NavigationStack per TabView tab without independent state">
1436
+ **Problem:** Sharing NavigationPath between tabs
1437
+
1438
+ **Why it's bad:**
1439
+ - Tabs should have independent navigation stacks
1440
+ - Switching tabs loses navigation context
1441
+ - Breaks expected tab behavior
1442
+
1443
+ **Instead:** Each tab gets its own NavigationStack and path
1444
+ ```swift
1445
+ // WRONG
1446
+ @State private var path = NavigationPath()
1447
+
1448
+ TabView {
1449
+ NavigationStack(path: $path) { HomeView() }
1450
+ .tabItem { Label("Home", systemImage: "house") }
1451
+
1452
+ NavigationStack(path: $path) { SearchView() } // ❌ Shared path
1453
+ .tabItem { Label("Search", systemImage: "magnifyingglass") }
1454
+ }
1455
+
1456
+ // RIGHT
1457
+ @State private var homePath = NavigationPath()
1458
+ @State private var searchPath = NavigationPath()
1459
+
1460
+ TabView {
1461
+ NavigationStack(path: $homePath) { HomeView() }
1462
+ .tabItem { Label("Home", systemImage: "house") }
1463
+
1464
+ NavigationStack(path: $searchPath) { SearchView() } // ✅ Independent
1465
+ .tabItem { Label("Search", systemImage: "magnifyingglass") }
1466
+ }
1467
+ ```
1468
+ </anti_pattern>
1469
+ </anti_patterns>
1470
+
1471
+ ## Sources
1472
+
1473
+ - [Hacking with Swift: Programmatic navigation with NavigationStack](https://www.hackingwithswift.com/books/ios-swiftui/programmatic-navigation-with-navigationstack)
1474
+ - [AzamSharp: Navigation Patterns in SwiftUI](https://azamsharp.com/2024/07/29/navigation-patterns-in-swiftui.html)
1475
+ - [tanaschita: How to use NavigationPath for routing in SwiftUI](https://tanaschita.com/swiftui-navigationpath/)
1476
+ - [Swift with Majid: Mastering NavigationStack in SwiftUI. Navigator Pattern](https://swiftwithmajid.com/2022/06/15/mastering-navigationstack-in-swiftui-navigator-pattern/)
1477
+ - [Medium: Mastering Navigation in SwiftUI: The 2025 Guide](https://medium.com/@dinaga119/mastering-navigation-in-swiftui-the-2025-guide-to-clean-scalable-routing-bbcb6dbce929)
1478
+ - [Swift Anytime: How to use Coordinator Pattern in SwiftUI](https://www.swiftanytime.com/blog/coordinator-pattern-in-swiftui)
1479
+ - [SwiftLee: Deeplink URL handling in SwiftUI](https://www.avanderlee.com/swiftui/deeplink-url-handling/)
1480
+ - [Michael Long: Advanced Deep Linking in SwiftUI](https://michaellong.medium.com/advanced-deep-linking-in-swiftui-c0085be83e7c)
1481
+ - [Swift with Majid: Deep linking for local notifications in SwiftUI](https://swiftwithmajid.com/2024/04/09/deep-linking-for-local-notifications-in-swiftui/)
1482
+ - [Sarunw: Bottom Sheet in SwiftUI on iOS 16 with presentationDetents](https://sarunw.com/posts/swiftui-bottom-sheet/)
1483
+ - [Apple Developer: presentationDetents(_:)](https://developer.apple.com/documentation/swiftui/view/presentationdetents(_:))
1484
+ - [Hacking with Swift: What's new in SwiftUI for iOS 18](https://www.hackingwithswift.com/articles/270/whats-new-in-swiftui-for-ios-18)
1485
+ - [iOS Coffee Break: Using SwiftUI's Improved TabView with Sidebar on iOS 18](https://www.ioscoffeebreak.com/issue/issue34)
1486
+ - [AppCoda: What's New in SwiftUI for iOS 18](https://www.appcoda.com/swiftui-ios-18/)
1487
+ - [Medium: Getting Started with the Improved TabView in iOS 18](https://medium.com/@jpmtech/getting-started-with-the-improved-tabview-in-ios-18-111974b70db9)
1488
+ - [Apple Developer: Enhancing your app's content with tab navigation](https://developer.apple.com/documentation/swiftui/enhancing-your-app-content-with-tab-navigation)
1489
+ - [Apple Developer: NavigationPath](https://developer.apple.com/documentation/swiftui/navigationpath)
1490
+ - [DEV Community: Modern Navigation in SwiftUI](https://dev.to/sebastienlato/modern-navigation-in-swiftui-1c8g)
1491
+ - [Medium: Mastering Navigation in SwiftUI Using Coordinator Pattern](https://medium.com/@dikidwid0/mastering-navigation-in-swiftui-using-coordinator-pattern-833396c67db5)
1492
+ - [QuickBird Studios: How to Use the Coordinator Pattern in SwiftUI](https://quickbirdstudios.com/blog/coordinator-pattern-in-swiftui/)