@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,1186 @@
1
+ <overview>
2
+ SwiftUI's layout system operates fundamentally differently from UIKit/Auto Layout. Instead of constraints, SwiftUI uses a **propose-measure-place** model:
3
+
4
+ 1. **Propose**: Parent offers child a size
5
+ 2. **Measure**: Child chooses its own size (parent must respect this)
6
+ 3. **Place**: Parent positions child in its coordinate space
7
+
8
+ This creates a declarative, predictable layout system where conflicts are impossible. SwiftUI always produces a valid layout.
9
+
10
+ **Read this file when:**
11
+ - Choosing between layout containers (HStack, VStack, Grid, etc.)
12
+ - Dealing with complex positioning requirements
13
+ - Performance tuning layouts with large datasets
14
+ - Understanding GeometryReader usage and alternatives
15
+
16
+ **See also:**
17
+ - `performance.md` for layout performance optimization strategies
18
+ - `architecture.md` for structuring complex view hierarchies
19
+ </overview>
20
+
21
+ <layout_containers>
22
+ ## Layout Containers
23
+
24
+ <container name="HStack">
25
+ **Purpose:** Horizontal arrangement of views from left to right (or right to left in RTL languages)
26
+
27
+ **Behavior:** Proposes equal width to all children, then distributes remaining space based on flexibility. Children choose their own heights.
28
+
29
+ **Alignment:** Default is `.center` vertically. Options: `.top`, `.center`, `.bottom`, `.firstTextBaseline`, `.lastTextBaseline`
30
+
31
+ **Spacing:** Default is system-defined (typically 8pt). Override with `spacing:` parameter or `.none` for zero spacing.
32
+
33
+ ```swift
34
+ // Common usage with custom spacing and alignment
35
+ HStack(alignment: .top, spacing: 12) {
36
+ Image(systemName: "person.circle")
37
+ .font(.largeTitle)
38
+
39
+ VStack(alignment: .leading, spacing: 4) {
40
+ Text("Username")
41
+ .font(.headline)
42
+ Text("Online")
43
+ .font(.caption)
44
+ .foregroundStyle(.secondary)
45
+ }
46
+
47
+ Spacer() // Pushes content to leading edge
48
+
49
+ Button("Follow") { }
50
+ }
51
+ .padding()
52
+ ```
53
+
54
+ **Performance:** Lightweight. All children are created immediately (not lazy).
55
+ </container>
56
+
57
+ <container name="VStack">
58
+ **Purpose:** Vertical arrangement of views from top to bottom
59
+
60
+ **Behavior:** Proposes equal height to all children, then distributes remaining space. Children choose their own widths.
61
+
62
+ **Alignment:** Default is `.center` horizontally. Options: `.leading`, `.center`, `.trailing`
63
+
64
+ **Spacing:** Default is system-defined. Override with `spacing:` parameter.
65
+
66
+ ```swift
67
+ // Card layout with multiple sections
68
+ VStack(alignment: .leading, spacing: 16) {
69
+ Text("Title")
70
+ .font(.headline)
71
+
72
+ Text("Body text that can span multiple lines and will wrap naturally within the available width.")
73
+ .font(.body)
74
+ .foregroundStyle(.secondary)
75
+
76
+ HStack {
77
+ Spacer()
78
+ Button("Action") { }
79
+ }
80
+ }
81
+ .padding()
82
+ .background(.background.secondary)
83
+ .clipShape(RoundedRectangle(cornerRadius: 12))
84
+ ```
85
+
86
+ **Performance:** Lightweight. All children are created immediately.
87
+ </container>
88
+
89
+ <container name="ZStack">
90
+ **Purpose:** Layering views on the Z-axis (depth), drawing from back to front
91
+
92
+ **Behavior:** Proposes full available size to all children. Final size is the union of all child sizes. Later views draw on top of earlier views.
93
+
94
+ **Alignment:** Default is `.center` both horizontally and vertically. Options include `.topLeading`, `.bottomTrailing`, etc.
95
+
96
+ ```swift
97
+ // Profile picture with badge
98
+ ZStack(alignment: .bottomTrailing) {
99
+ AsyncImage(url: profileURL) { image in
100
+ image
101
+ .resizable()
102
+ .scaledToFill()
103
+ } placeholder: {
104
+ Color.gray
105
+ }
106
+ .frame(width: 100, height: 100)
107
+ .clipShape(Circle())
108
+
109
+ // Notification badge
110
+ Circle()
111
+ .fill(.red)
112
+ .frame(width: 24, height: 24)
113
+ .overlay {
114
+ Text("3")
115
+ .font(.caption2.bold())
116
+ .foregroundStyle(.white)
117
+ }
118
+ .offset(x: 4, y: 4)
119
+ }
120
+ ```
121
+
122
+ **Performance:** Lightweight. Avoid excessive layering for complex effects (use `.overlay()` or `.background()` instead when appropriate).
123
+ </container>
124
+
125
+ <container name="LazyVStack">
126
+ **Purpose:** Vertical stack with deferred view creation - only creates views when they scroll into view
127
+
128
+ **When to use:**
129
+ - Lists with hundreds or thousands of items
130
+ - Items with expensive initialization (images, complex views)
131
+ - Memory-constrained scenarios
132
+
133
+ **Difference from VStack:**
134
+ - VStack creates all children immediately
135
+ - LazyVStack creates children on-demand as they appear
136
+ - LazyVStack requires a ScrollView parent
137
+ - LazyVStack calculates layout incrementally
138
+
139
+ ```swift
140
+ ScrollView {
141
+ LazyVStack(spacing: 12, pinnedViews: [.sectionHeaders]) {
142
+ Section {
143
+ ForEach(items) { item in
144
+ ItemRow(item: item)
145
+ .frame(height: 60)
146
+ }
147
+ } header: {
148
+ Text("Section Header")
149
+ .font(.headline)
150
+ .padding()
151
+ .frame(maxWidth: .infinity, alignment: .leading)
152
+ .background(.ultraThinMaterial)
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ **Performance:**
159
+ - Superior for long lists (only renders visible views)
160
+ - Slight overhead per view creation
161
+ - Use `.id()` modifier to control view identity/reuse
162
+
163
+ **See also:** LazyHStack for horizontal lazy loading
164
+ </container>
165
+
166
+ <container name="LazyVGrid / LazyHGrid">
167
+ **Purpose:** Grid layouts with lazy loading - creates cells only when visible
168
+
169
+ **GridItem types:**
170
+ - `.fixed(width)`: Exactly the specified width
171
+ - `.flexible(minimum:maximum:)`: Grows to fill space within bounds
172
+ - `.adaptive(minimum:maximum:)`: Creates as many columns as fit
173
+
174
+ ```swift
175
+ // Photo grid with adaptive columns
176
+ ScrollView {
177
+ LazyVGrid(
178
+ columns: [GridItem(.adaptive(minimum: 120, maximum: 200))],
179
+ spacing: 12
180
+ ) {
181
+ ForEach(photos) { photo in
182
+ AsyncImage(url: photo.url) { image in
183
+ image
184
+ .resizable()
185
+ .scaledToFill()
186
+ } placeholder: {
187
+ Color.gray.opacity(0.3)
188
+ }
189
+ .frame(height: 120)
190
+ .clipShape(RoundedRectangle(cornerRadius: 8))
191
+ }
192
+ }
193
+ .padding()
194
+ }
195
+
196
+ // Fixed column layout (3 columns)
197
+ let columns = [
198
+ GridItem(.flexible()),
199
+ GridItem(.flexible()),
200
+ GridItem(.flexible())
201
+ ]
202
+
203
+ LazyVGrid(columns: columns, spacing: 16) {
204
+ ForEach(items) { item in
205
+ ItemCard(item: item)
206
+ }
207
+ }
208
+ ```
209
+
210
+ **Performance:**
211
+ - Only creates visible cells (significant memory savings)
212
+ - Best for image galleries, product catalogs, large datasets
213
+ - On macOS, performance may drop below UIKit CollectionView for very large datasets (see performance.md)
214
+ - Consider pagination for datasets over 1000 items
215
+
216
+ **When NOT to use:** Small grids (< 20 items) - use Grid instead for simpler code
217
+ </container>
218
+
219
+ <container name="Grid (iOS 16+)">
220
+ **Purpose:** Non-lazy grid with explicit row/column control and advanced alignment
221
+
222
+ **When to use:**
223
+ - Small datasets where all items can be in memory
224
+ - Need precise row/column control
225
+ - Need GridRow for custom row styling
226
+ - Need alignment across cells in different rows
227
+
228
+ **Difference from LazyVGrid:**
229
+ - Grid creates all cells immediately
230
+ - Grid gives more layout control (GridRow, cell spanning)
231
+ - Grid supports baseline alignment across rows
232
+ - Grid is simpler for static content
233
+
234
+ ```swift
235
+ // Form-like layout with alignment
236
+ Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
237
+ GridRow {
238
+ Text("Name:")
239
+ .gridColumnAlignment(.trailing)
240
+ TextField("Enter name", text: $name)
241
+ }
242
+
243
+ GridRow {
244
+ Text("Email:")
245
+ .gridColumnAlignment(.trailing)
246
+ TextField("Enter email", text: $email)
247
+ }
248
+
249
+ GridRow {
250
+ Color.clear
251
+ .gridCellUnsizedAxes(.horizontal)
252
+
253
+ Button("Submit") { }
254
+ .gridCellColumns(1)
255
+ }
256
+ }
257
+ .padding()
258
+
259
+ // Spanning cells
260
+ Grid {
261
+ GridRow {
262
+ Text("Header")
263
+ .gridCellColumns(3) // Spans 3 columns
264
+ .font(.headline)
265
+ }
266
+
267
+ GridRow {
268
+ ForEach(1...3, id: \.self) { num in
269
+ Text("Cell \(num)")
270
+ }
271
+ }
272
+ }
273
+ ```
274
+
275
+ **Performance:** All cells created immediately. Keep under 100 items or use LazyVGrid.
276
+ </container>
277
+ </layout_containers>
278
+
279
+ <geometry_reader>
280
+ ## GeometryReader
281
+
282
+ **Purpose:** Access parent's proposed size and safe area insets for custom layout calculations
283
+
284
+ **When to use:**
285
+ - Custom drawing or graphics that need exact dimensions
286
+ - Complex animations requiring precise positioning
287
+ - Creating custom layout effects not possible with standard containers
288
+ - Reading coordinate spaces for gesture calculations
289
+
290
+ **When NOT to use:**
291
+ - Simple relative sizing → use `.frame(maxWidth: .infinity)` or `Spacer()`
292
+ - Container-relative frames → use `containerRelativeFrame()` (iOS 17+)
293
+ - Adaptive layouts → use `ViewThatFits` (iOS 16+)
294
+ - Safe area queries → use safe area modifiers directly
295
+
296
+ ```swift
297
+ // Correct usage: Custom circular progress
298
+ GeometryReader { geometry in
299
+ ZStack {
300
+ Circle()
301
+ .stroke(Color.gray.opacity(0.3), lineWidth: 10)
302
+
303
+ Circle()
304
+ .trim(from: 0, to: progress)
305
+ .stroke(Color.blue, style: StrokeStyle(lineWidth: 10, lineCap: .round))
306
+ .rotationEffect(.degrees(-90))
307
+ .animation(.easeInOut, value: progress)
308
+ }
309
+ .frame(width: geometry.size.width, height: geometry.size.width)
310
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
311
+ }
312
+ .aspectRatio(1, contentMode: .fit)
313
+
314
+ // Correct usage: Reading coordinate spaces
315
+ struct DragView: View {
316
+ @State private var location: CGPoint = .zero
317
+
318
+ var body: some View {
319
+ GeometryReader { geometry in
320
+ Circle()
321
+ .fill(.blue)
322
+ .frame(width: 50, height: 50)
323
+ .position(location)
324
+ .gesture(
325
+ DragGesture(coordinateSpace: .named("container"))
326
+ .onChanged { value in
327
+ location = value.location
328
+ }
329
+ )
330
+ }
331
+ .coordinateSpace(name: "container")
332
+ }
333
+ }
334
+ ```
335
+
336
+ **Pitfalls:**
337
+
338
+ 1. **Expands to fill all available space**: GeometryReader acts like `Color.clear.frame(maxWidth: .infinity, maxHeight: .infinity)`, which breaks layouts in ScrollViews and can cause infinite height calculations
339
+
340
+ 2. **Breaks ScrollView behavior**: Inside ScrollView, GeometryReader tries to fill infinite space, causing layout loops and broken scrolling
341
+
342
+ 3. **Overused for simple tasks**: Most GeometryReader usage can be replaced with simpler solutions
343
+
344
+ 4. **Deep nesting causes unpredictable behavior**: Nested GeometryReaders compound layout issues
345
+
346
+ 5. **Performance overhead**: Rebuilds view on every geometry change
347
+
348
+ **Alternatives to GeometryReader:**
349
+
350
+ ```swift
351
+ // ❌ BAD: Using GeometryReader for container-relative sizing
352
+ GeometryReader { geometry in
353
+ Rectangle()
354
+ .frame(width: geometry.size.width * 0.5)
355
+ }
356
+
357
+ // ✅ GOOD: Use containerRelativeFrame (iOS 17+)
358
+ Rectangle()
359
+ .containerRelativeFrame(.horizontal) { width, _ in
360
+ width * 0.5
361
+ }
362
+
363
+ // ✅ GOOD: Use frame modifiers
364
+ Rectangle()
365
+ .frame(maxWidth: .infinity)
366
+ .padding(.horizontal, .infinity) // Creates 50% width
367
+
368
+ // ❌ BAD: GeometryReader for adaptive layouts
369
+ GeometryReader { geometry in
370
+ if geometry.size.width > 600 {
371
+ HStack { content }
372
+ } else {
373
+ VStack { content }
374
+ }
375
+ }
376
+
377
+ // ✅ GOOD: Use ViewThatFits (iOS 16+)
378
+ ViewThatFits {
379
+ HStack { content }
380
+ VStack { content }
381
+ }
382
+ ```
383
+
384
+ **Using GeometryReader safely:**
385
+
386
+ ```swift
387
+ // Use in .background() or .overlay() to avoid affecting layout
388
+ Text("Hello")
389
+ .background(
390
+ GeometryReader { geometry in
391
+ Color.clear
392
+ .onAppear {
393
+ print("Size: \(geometry.size)")
394
+ }
395
+ }
396
+ )
397
+ ```
398
+
399
+ **See also:** `performance.md` for GeometryReader performance considerations
400
+ </geometry_reader>
401
+
402
+ <custom_layout>
403
+ ## Custom Layout Protocol (iOS 16+)
404
+
405
+ **When to use:**
406
+ - Standard containers cannot achieve the desired layout
407
+ - Flow/tag layouts (wrapping items like a text paragraph)
408
+ - Radial/circular arrangements
409
+ - Custom grid behaviors (masonry, Pinterest-style)
410
+ - Complex alignment requirements across multiple views
411
+
412
+ **Protocol requirements:**
413
+ 1. `sizeThatFits(proposal:subviews:cache:)`: Calculate and return container size
414
+ 2. `placeSubviews(in:proposal:subviews:cache:)`: Position each subview
415
+
416
+ **Optional:**
417
+ - `makeCache(subviews:)`: Create shared computation cache
418
+ - `updateCache(_:subviews:)`: Update cache when subviews change
419
+ - `explicitAlignment(of:in:proposal:subviews:cache:)`: Define custom alignment guides
420
+
421
+ ```swift
422
+ // Complete FlowLayout example (tag cloud, wrapping items)
423
+ struct FlowLayout: Layout {
424
+ var spacing: CGFloat = 8
425
+
426
+ func sizeThatFits(
427
+ proposal: ProposedViewSize,
428
+ subviews: Subviews,
429
+ cache: inout Cache
430
+ ) -> CGSize {
431
+ let rows = computeRows(proposal: proposal, subviews: subviews)
432
+
433
+ let width = proposal.replacingUnspecifiedDimensions().width
434
+ let height = rows.reduce(0) { $0 + $1.height + spacing } - spacing
435
+
436
+ return CGSize(width: width, height: height)
437
+ }
438
+
439
+ func placeSubviews(
440
+ in bounds: CGRect,
441
+ proposal: ProposedViewSize,
442
+ subviews: Subviews,
443
+ cache: inout Cache
444
+ ) {
445
+ let rows = computeRows(proposal: proposal, subviews: subviews)
446
+
447
+ var y = bounds.minY
448
+ for row in rows {
449
+ var x = bounds.minX
450
+
451
+ for index in row.subviewIndices {
452
+ let subview = subviews[index]
453
+ let size = subview.sizeThatFits(.unspecified)
454
+
455
+ subview.place(
456
+ at: CGPoint(x: x, y: y),
457
+ proposal: ProposedViewSize(size)
458
+ )
459
+
460
+ x += size.width + spacing
461
+ }
462
+
463
+ y += row.height + spacing
464
+ }
465
+ }
466
+
467
+ // Cache structure for performance
468
+ struct Cache {
469
+ var rows: [Row] = []
470
+ }
471
+
472
+ struct Row {
473
+ var subviewIndices: [Int]
474
+ var height: CGFloat
475
+ }
476
+
477
+ private func computeRows(
478
+ proposal: ProposedViewSize,
479
+ subviews: Subviews
480
+ ) -> [Row] {
481
+ let width = proposal.replacingUnspecifiedDimensions().width
482
+ var rows: [Row] = []
483
+ var currentRow: [Int] = []
484
+ var currentX: CGFloat = 0
485
+ var currentHeight: CGFloat = 0
486
+
487
+ for (index, subview) in subviews.enumerated() {
488
+ let size = subview.sizeThatFits(.unspecified)
489
+
490
+ if currentX + size.width > width && !currentRow.isEmpty {
491
+ // Start new row
492
+ rows.append(Row(subviewIndices: currentRow, height: currentHeight))
493
+ currentRow = []
494
+ currentX = 0
495
+ currentHeight = 0
496
+ }
497
+
498
+ currentRow.append(index)
499
+ currentX += size.width + spacing
500
+ currentHeight = max(currentHeight, size.height)
501
+ }
502
+
503
+ if !currentRow.isEmpty {
504
+ rows.append(Row(subviewIndices: currentRow, height: currentHeight))
505
+ }
506
+
507
+ return rows
508
+ }
509
+ }
510
+
511
+ // Usage
512
+ FlowLayout(spacing: 12) {
513
+ ForEach(tags, id: \.self) { tag in
514
+ Text(tag)
515
+ .padding(.horizontal, 12)
516
+ .padding(.vertical, 6)
517
+ .background(.blue.opacity(0.2))
518
+ .clipShape(Capsule())
519
+ }
520
+ }
521
+
522
+ // Radial layout example
523
+ struct RadialLayout: Layout {
524
+ func sizeThatFits(
525
+ proposal: ProposedViewSize,
526
+ subviews: Subviews,
527
+ cache: inout ()
528
+ ) -> CGSize {
529
+ proposal.replacingUnspecifiedDimensions()
530
+ }
531
+
532
+ func placeSubviews(
533
+ in bounds: CGRect,
534
+ proposal: ProposedViewSize,
535
+ subviews: Subviews,
536
+ cache: inout ()
537
+ ) {
538
+ let radius = min(bounds.width, bounds.height) / 2.5
539
+ let center = CGPoint(x: bounds.midX, y: bounds.midY)
540
+ let angle = (2 * .pi) / Double(subviews.count)
541
+
542
+ for (index, subview) in subviews.enumerated() {
543
+ let theta = angle * Double(index) - .pi / 2
544
+ let x = center.x + radius * cos(theta)
545
+ let y = center.y + radius * sin(theta)
546
+
547
+ subview.place(
548
+ at: CGPoint(x: x, y: y),
549
+ anchor: .center,
550
+ proposal: .unspecified
551
+ )
552
+ }
553
+ }
554
+ }
555
+ ```
556
+
557
+ **Use cases:**
558
+ - **Flow/tag layout**: Wrapping items like tags, badges, or chips
559
+ - **Radial layout**: Circular menu, dial controls
560
+ - **Masonry grid**: Pinterest-style uneven grid
561
+ - **Custom calendar**: Week views with variable heights
562
+ - **Waterfall layout**: Staggered grid with varying item heights
563
+
564
+ **See also:**
565
+ - Apple's official documentation: [Composing custom layouts with SwiftUI](https://developer.apple.com/documentation/swiftui/composing_custom_layouts_with_swiftui)
566
+ - `performance.md` for Layout protocol caching strategies
567
+ </custom_layout>
568
+
569
+ <alignment_guides>
570
+ ## Alignment and Alignment Guides
571
+
572
+ **Built-in alignments:**
573
+ - **Vertical**: `.leading`, `.center`, `.trailing`
574
+ - **Horizontal**: `.top`, `.center`, `.bottom`, `.firstTextBaseline`, `.lastTextBaseline`
575
+
576
+ **How alignment works:** When containers like HStack or VStack align children, they use alignment guides. Each view exposes alignment guide values, and the container aligns those values across children.
577
+
578
+ **Custom alignment guides:**
579
+
580
+ ```swift
581
+ // Define custom vertical alignment
582
+ extension VerticalAlignment {
583
+ private struct MidAccountAndName: AlignmentID {
584
+ static func defaultValue(in context: ViewDimensions) -> CGFloat {
585
+ context[VerticalAlignment.center]
586
+ }
587
+ }
588
+
589
+ static let midAccountAndName = VerticalAlignment(MidAccountAndName.self)
590
+ }
591
+
592
+ // Use custom alignment
593
+ HStack(alignment: .midAccountAndName) {
594
+ VStack(alignment: .trailing) {
595
+ Text("Full Name:")
596
+ Text("Address:")
597
+ Text("Account Number:")
598
+ .alignmentGuide(.midAccountAndName) { d in
599
+ d[VerticalAlignment.center]
600
+ }
601
+ }
602
+
603
+ VStack(alignment: .leading) {
604
+ Text("John Doe")
605
+ Text("123 Main St")
606
+ Text("98765-4321")
607
+ .alignmentGuide(.midAccountAndName) { d in
608
+ d[VerticalAlignment.center]
609
+ }
610
+ }
611
+ }
612
+
613
+ // Adjusting alignment dynamically
614
+ Image(systemName: "arrow.up")
615
+ .alignmentGuide(.leading) { d in
616
+ d[.leading] - 50 // Shift left by 50 points
617
+ }
618
+ ```
619
+
620
+ **Preference keys for custom alignment:**
621
+
622
+ Preference keys allow child views to communicate layout information up the hierarchy.
623
+
624
+ ```swift
625
+ // Define preference key for collecting bounds
626
+ struct BoundsPreferenceKey: PreferenceKey {
627
+ static var defaultValue: [String: Anchor<CGRect>] = [:]
628
+
629
+ static func reduce(
630
+ value: inout [String: Anchor<CGRect>],
631
+ nextValue: () -> [String: Anchor<CGRect]]
632
+ ) {
633
+ value.merge(nextValue(), uniquingKeysWith: { $1 })
634
+ }
635
+ }
636
+
637
+ // Use anchorPreference to collect child bounds
638
+ struct HighlightableView: View {
639
+ @State private var highlightedFrame: CGRect?
640
+
641
+ var body: some View {
642
+ VStack(spacing: 20) {
643
+ Text("First")
644
+ .padding()
645
+ .background(Color.blue.opacity(0.3))
646
+ .anchorPreference(
647
+ key: BoundsPreferenceKey.self,
648
+ value: .bounds
649
+ ) {
650
+ ["first": $0]
651
+ }
652
+
653
+ Text("Second")
654
+ .padding()
655
+ .background(Color.green.opacity(0.3))
656
+ .anchorPreference(
657
+ key: BoundsPreferenceKey.self,
658
+ value: .bounds
659
+ ) {
660
+ ["second": $0]
661
+ }
662
+ }
663
+ .overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
664
+ GeometryReader { geometry in
665
+ if let anchor = preferences["first"],
666
+ let frame = highlightedFrame {
667
+ Rectangle()
668
+ .stroke(Color.red, lineWidth: 2)
669
+ .frame(width: frame.width, height: frame.height)
670
+ .position(x: frame.midX, y: frame.midY)
671
+ }
672
+ }
673
+ }
674
+ .onPreferenceChange(BoundsPreferenceKey.self) { preferences in
675
+ // React to preference changes
676
+ }
677
+ }
678
+ }
679
+ ```
680
+
681
+ **Practical use cases:**
682
+ - Aligning labels across separate VStacks
683
+ - Syncing baseline alignment in complex layouts
684
+ - Cross-highlighting related views
685
+ - Creating connection lines between views
686
+
687
+ **See also:**
688
+ - [The SwiftUI Lab: Alignment Guides](https://swiftui-lab.com/alignment-guides/)
689
+ - [Swift with Majid: Layout alignment](https://fatbobman.com/en/posts/layout-alignment/)
690
+ </alignment_guides>
691
+
692
+ <safe_areas>
693
+ ## Safe Area Handling
694
+
695
+ SwiftUI respects safe areas by default (avoiding notches, home indicator, status bar).
696
+
697
+ **Three key modifiers:**
698
+
699
+ ### `ignoresSafeArea(_:edges:)`
700
+ **When to use:** Extend backgrounds or images edge-to-edge while keeping content safe
701
+
702
+ ```swift
703
+ // Background that extends to edges
704
+ ZStack {
705
+ Color.blue
706
+ .ignoresSafeArea() // Goes edge-to-edge
707
+
708
+ VStack {
709
+ Text("Content")
710
+ Spacer()
711
+ }
712
+ .padding() // Content stays in safe area
713
+ }
714
+
715
+ // Ignore only specific edges
716
+ ScrollView {
717
+ content
718
+ }
719
+ .ignoresSafeArea(.container, edges: .bottom)
720
+
721
+ // Ignore keyboard safe area
722
+ TextField("Message", text: $message)
723
+ .ignoresSafeArea(.keyboard)
724
+ ```
725
+
726
+ **Regions:**
727
+ - `.container`: Device edges, status bar, home indicator
728
+ - `.keyboard`: Soft keyboard area
729
+ - `.all`: Both container and keyboard
730
+
731
+ **Edges:** `.top`, `.bottom`, `.leading`, `.trailing`, `.horizontal`, `.vertical`, `.all`
732
+
733
+ ### `safeAreaInset(edge:alignment:spacing:content:)`
734
+ **When to use:** Add custom bars (toolbars, tab bars) that shrink the safe area for other content
735
+
736
+ ```swift
737
+ // Custom bottom toolbar
738
+ ScrollView {
739
+ ForEach(items) { item in
740
+ ItemRow(item: item)
741
+ }
742
+ }
743
+ .safeAreaInset(edge: .bottom, spacing: 0) {
744
+ HStack {
745
+ Button("Action 1") { }
746
+ Spacer()
747
+ Button("Action 2") { }
748
+ }
749
+ .padding()
750
+ .background(.ultraThinMaterial)
751
+ }
752
+
753
+ // Multiple insets stack
754
+ ScrollView {
755
+ content
756
+ }
757
+ .safeAreaInset(edge: .top) {
758
+ SearchBar()
759
+ }
760
+ .safeAreaInset(edge: .bottom) {
761
+ BottomBar()
762
+ }
763
+
764
+ // Inset with custom alignment
765
+ List(messages) { message in
766
+ MessageRow(message: message)
767
+ }
768
+ .safeAreaInset(edge: .bottom, alignment: .trailing) {
769
+ Button(action: compose) {
770
+ Image(systemName: "plus.circle.fill")
771
+ .font(.largeTitle)
772
+ }
773
+ .padding()
774
+ }
775
+ ```
776
+
777
+ **Key behavior:** Unlike `ignoresSafeArea`, this **shrinks** the safe area so other views avoid it.
778
+
779
+ ### `safeAreaPadding(_:_:)` (iOS 17+)
780
+ **When to use:** Extend safe area by a fixed amount without providing a view
781
+
782
+ ```swift
783
+ // Add padding to safe area
784
+ ScrollView {
785
+ content
786
+ }
787
+ .safeAreaPadding(.horizontal, 20)
788
+ .safeAreaPadding(.bottom, 60)
789
+
790
+ // Equivalent to safeAreaInset but cleaner when you don't need a view
791
+ ```
792
+
793
+ **Difference from `.padding()`:**
794
+ - `.padding()` adds space but doesn't affect safe area calculations
795
+ - `.safeAreaPadding()` extends the safe area itself
796
+
797
+ ### Accessing safe area values
798
+
799
+ ```swift
800
+ // Read safe area insets
801
+ GeometryReader { geometry in
802
+ let safeArea = geometry.safeAreaInsets
803
+
804
+ VStack {
805
+ Text("Top: \(safeArea.top)")
806
+ Text("Bottom: \(safeArea.bottom)")
807
+ }
808
+ }
809
+ ```
810
+
811
+ **See also:** [Managing safe area in SwiftUI](https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/)
812
+ </safe_areas>
813
+
814
+ <decision_tree>
815
+ ## Choosing the Right Layout
816
+
817
+ **Simple horizontal arrangement:**
818
+ - Use `HStack` with alignment and spacing parameters
819
+ - Use `Spacer()` to push content to edges
820
+
821
+ **Simple vertical arrangement:**
822
+ - Use `VStack` with alignment and spacing parameters
823
+ - Consider `LazyVStack` if list exceeds 50+ items
824
+
825
+ **Overlapping views:**
826
+ - Use `ZStack` for basic layering
827
+ - Use `.overlay()` or `.background()` for single overlay/underlay
828
+ - Consider Custom Layout for complex Z-ordering logic
829
+
830
+ **Long scrolling list:**
831
+ - Use `LazyVStack` inside `ScrollView` for variable content
832
+ - Use `List` for standard iOS list appearance with built-in features (swipe actions, separators)
833
+ - Vertical scrolling: `ScrollView { LazyVStack { } }`
834
+ - Horizontal scrolling: `ScrollView(.horizontal) { LazyHStack { } }`
835
+
836
+ **Grid of items:**
837
+ - **Small grid (< 20 items):** Use `Grid` for full control
838
+ - **Large grid:** Use `LazyVGrid` or `LazyHGrid`
839
+ - **Fixed columns:** `LazyVGrid(columns: [GridItem(.flexible()), ...])`
840
+ - **Adaptive columns:** `LazyVGrid(columns: [GridItem(.adaptive(minimum: 120))])`
841
+
842
+ **Need parent size:**
843
+ - **iOS 17+:** Use `containerRelativeFrame()` for size relative to container
844
+ - **iOS 16+:** Use `ViewThatFits` for adaptive layouts
845
+ - **Custom drawing/gestures:** Use `GeometryReader` sparingly
846
+ - **Simple fills:** Use `.frame(maxWidth: .infinity)`
847
+
848
+ **Adaptive layout (changes based on space):**
849
+ - Use `ViewThatFits` (iOS 16+) to switch between layouts
850
+ - Use size classes with `@Environment(\.horizontalSizeClass)`
851
+
852
+ **Complex custom layout:**
853
+ - Implement Custom Layout protocol (iOS 16+)
854
+ - Use for: flow layouts, radial layouts, masonry grids
855
+ - Provides full control over sizing and positioning
856
+
857
+ **Performance considerations:**
858
+
859
+ | Scenario | Recommendation | Reason |
860
+ |----------|---------------|--------|
861
+ | Static grid < 20 items | Grid | Simpler, all layout upfront |
862
+ | Dynamic list 50+ items | LazyVStack | Only renders visible |
863
+ | Photo gallery 100+ items | LazyVGrid | Memory efficient |
864
+ | Constantly changing list | LazyVStack with `.id()` | Controls view identity |
865
+ | macOS high FPS requirement | UIKit/AppKit wrapper | SwiftUI grids cap at ~90fps |
866
+ | Complex nesting 5+ levels | Custom Layout | Better control, fewer containers |
867
+
868
+ **See also:** `performance.md` for detailed performance tuning strategies
869
+ </decision_tree>
870
+
871
+ <anti_patterns>
872
+ ## What NOT to Do
873
+
874
+ <anti_pattern name="GeometryReader for everything">
875
+ **Problem:** Using GeometryReader when simpler solutions exist
876
+
877
+ **Example:**
878
+ ```swift
879
+ // ❌ Overcomplicated
880
+ GeometryReader { geometry in
881
+ Rectangle()
882
+ .frame(width: geometry.size.width * 0.8)
883
+ }
884
+
885
+ // ✅ Simple and correct
886
+ Rectangle()
887
+ .frame(maxWidth: .infinity)
888
+ .padding(.horizontal, 40) // Creates inset
889
+ ```
890
+
891
+ **Why it's bad:**
892
+ - GeometryReader expands to fill all space, breaking layouts
893
+ - Causes performance overhead
894
+ - Makes code harder to understand
895
+ - Often causes issues in ScrollViews
896
+
897
+ **Instead:**
898
+ - Use `.frame(maxWidth: .infinity)` for full width
899
+ - Use `containerRelativeFrame()` (iOS 17+) for proportional sizing
900
+ - Use `ViewThatFits` (iOS 16+) for adaptive layouts
901
+ - Reserve GeometryReader for actual coordinate-space needs
902
+ </anti_pattern>
903
+
904
+ <anti_pattern name="Nested Stacks explosion">
905
+ **Problem:** Excessive nesting of HStack/VStack creating deep hierarchies
906
+
907
+ **Example:**
908
+ ```swift
909
+ // ❌ Too many nested stacks
910
+ VStack {
911
+ HStack {
912
+ VStack {
913
+ HStack {
914
+ VStack {
915
+ Text("Title")
916
+ Text("Subtitle")
917
+ }
918
+ }
919
+ }
920
+ }
921
+ }
922
+
923
+ // ✅ Flattened with proper modifiers
924
+ VStack(alignment: .leading, spacing: 4) {
925
+ Text("Title")
926
+ .font(.headline)
927
+ Text("Subtitle")
928
+ .font(.subheadline)
929
+ .foregroundStyle(.secondary)
930
+ }
931
+ ```
932
+
933
+ **Why it's bad:**
934
+ - Harder to read and maintain
935
+ - Unnecessary view hierarchy depth
936
+ - Can impact performance with many views
937
+ - Makes alignment more complex
938
+
939
+ **Instead:**
940
+ - Use alignment and spacing parameters instead of wrapper stacks
941
+ - Extract complex views into separate components
942
+ - Use Grid for form-like layouts
943
+ - Consider Custom Layout for truly complex arrangements
944
+ </anti_pattern>
945
+
946
+ <anti_pattern name="LazyVStack without ScrollView">
947
+ **Problem:** Using LazyVStack outside a ScrollView
948
+
949
+ **Example:**
950
+ ```swift
951
+ // ❌ LazyVStack needs a scrollable container
952
+ LazyVStack {
953
+ ForEach(items) { item in
954
+ Text(item.name)
955
+ }
956
+ }
957
+
958
+ // ✅ Correct usage
959
+ ScrollView {
960
+ LazyVStack {
961
+ ForEach(items) { item in
962
+ Text(item.name)
963
+ }
964
+ }
965
+ }
966
+
967
+ // ✅ Or just use VStack if not scrolling
968
+ VStack {
969
+ ForEach(items) { item in
970
+ Text(item.name)
971
+ }
972
+ }
973
+ ```
974
+
975
+ **Why it's bad:**
976
+ - LazyVStack requires a scrollable parent to know when to load views
977
+ - Without scrolling, there's no benefit to lazy loading
978
+ - Can cause unexpected layout behavior
979
+
980
+ **Instead:**
981
+ - Always wrap LazyVStack/LazyHStack in ScrollView
982
+ - If not scrolling, use regular VStack/HStack
983
+ </anti_pattern>
984
+
985
+ <anti_pattern name="Fixed GridItem sizes everywhere">
986
+ **Problem:** Using `.fixed()` GridItem when flexible sizing would work better
987
+
988
+ **Example:**
989
+ ```swift
990
+ // ❌ Fixed sizes break on different screen sizes
991
+ LazyVGrid(columns: [
992
+ GridItem(.fixed(150)),
993
+ GridItem(.fixed(150))
994
+ ]) {
995
+ ForEach(items) { item in
996
+ ItemView(item: item)
997
+ }
998
+ }
999
+
1000
+ // ✅ Adaptive sizing
1001
+ LazyVGrid(columns: [
1002
+ GridItem(.adaptive(minimum: 150, maximum: 200))
1003
+ ]) {
1004
+ ForEach(items) { item in
1005
+ ItemView(item: item)
1006
+ }
1007
+ }
1008
+
1009
+ // ✅ Flexible columns
1010
+ LazyVGrid(columns: [
1011
+ GridItem(.flexible()),
1012
+ GridItem(.flexible())
1013
+ ]) {
1014
+ ForEach(items) { item in
1015
+ ItemView(item: item)
1016
+ }
1017
+ }
1018
+ ```
1019
+
1020
+ **Why it's bad:**
1021
+ - Doesn't adapt to different screen sizes (iPhone SE vs iPad)
1022
+ - Creates horizontal scrolling or cut-off content
1023
+ - Not responsive to orientation changes
1024
+
1025
+ **Instead:**
1026
+ - Use `.flexible()` to let items share space proportionally
1027
+ - Use `.adaptive()` to fit as many items as possible
1028
+ - Reserve `.fixed()` for specific design requirements (icons, avatars)
1029
+ </anti_pattern>
1030
+
1031
+ <anti_pattern name="Spacer() abuse">
1032
+ **Problem:** Using multiple Spacers when alignment parameters would be clearer
1033
+
1034
+ **Example:**
1035
+ ```swift
1036
+ // ❌ Confusing spacer usage
1037
+ HStack {
1038
+ Spacer()
1039
+ Text("Centered?")
1040
+ Spacer()
1041
+ Spacer()
1042
+ }
1043
+
1044
+ // ✅ Clear alignment
1045
+ HStack {
1046
+ Spacer()
1047
+ Text("Centered")
1048
+ Spacer()
1049
+ }
1050
+
1051
+ // ✅ Even better - use alignment
1052
+ HStack {
1053
+ Text("Centered")
1054
+ }
1055
+ .frame(maxWidth: .infinity)
1056
+
1057
+ // ✅ For trailing alignment
1058
+ HStack {
1059
+ Spacer()
1060
+ Text("Trailing")
1061
+ }
1062
+ ```
1063
+
1064
+ **Why it's bad:**
1065
+ - Multiple spacers create ambiguous spacing
1066
+ - Harder to reason about layout
1067
+ - Can cause unexpected behavior with different content sizes
1068
+
1069
+ **Instead:**
1070
+ - Use single Spacer() for clear intent
1071
+ - Use frame modifiers with alignment
1072
+ - Use stack alignment parameters
1073
+ </anti_pattern>
1074
+
1075
+ <anti_pattern name="Mixing lazy and non-lazy inappropriately">
1076
+ **Problem:** Using LazyVStack for small lists or VStack for huge lists
1077
+
1078
+ **Example:**
1079
+ ```swift
1080
+ // ❌ Lazy overhead for tiny list
1081
+ ScrollView {
1082
+ LazyVStack {
1083
+ ForEach(0..<5) { i in
1084
+ Text("Item \(i)")
1085
+ }
1086
+ }
1087
+ }
1088
+
1089
+ // ✅ Just use VStack
1090
+ ScrollView {
1091
+ VStack {
1092
+ ForEach(0..<5) { i in
1093
+ Text("Item \(i)")
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ // ❌ Regular stack for huge list
1099
+ VStack {
1100
+ ForEach(0..<1000) { i in
1101
+ ExpensiveView(index: i)
1102
+ }
1103
+ }
1104
+
1105
+ // ✅ Lazy for performance
1106
+ ScrollView {
1107
+ LazyVStack {
1108
+ ForEach(0..<1000) { i in
1109
+ ExpensiveView(index: i)
1110
+ }
1111
+ }
1112
+ }
1113
+ ```
1114
+
1115
+ **Why it's bad:**
1116
+ - Lazy containers add overhead for small datasets
1117
+ - Non-lazy containers create all views upfront (memory/performance hit)
1118
+
1119
+ **Instead:**
1120
+ - **< 20 simple items:** Use VStack/HStack
1121
+ - **20-50 items:** Test both; likely VStack is fine
1122
+ - **> 50 items or complex views:** Use LazyVStack/LazyHStack
1123
+ - **Large images/media:** Always use lazy
1124
+ </anti_pattern>
1125
+
1126
+ <anti_pattern name="ViewThatFits with fixed frames">
1127
+ **Problem:** Providing fixed frames to ViewThatFits children, defeating its purpose
1128
+
1129
+ **Example:**
1130
+ ```swift
1131
+ // ❌ Fixed frames prevent ViewThatFits from working
1132
+ ViewThatFits {
1133
+ HStack {
1134
+ content
1135
+ }
1136
+ .frame(width: 600) // Prevents fitting logic
1137
+
1138
+ VStack {
1139
+ content
1140
+ }
1141
+ .frame(width: 300)
1142
+ }
1143
+
1144
+ // ✅ Let views size naturally
1145
+ ViewThatFits {
1146
+ HStack {
1147
+ content
1148
+ }
1149
+
1150
+ VStack {
1151
+ content
1152
+ }
1153
+ }
1154
+ ```
1155
+
1156
+ **Why it's bad:**
1157
+ - ViewThatFits needs to measure ideal sizes to choose the right view
1158
+ - Fixed frames override this measurement
1159
+ - Defeats the entire purpose of adaptive layout
1160
+
1161
+ **Instead:**
1162
+ - Let child views size themselves naturally
1163
+ - Use maxWidth/maxHeight if needed, not fixed sizes
1164
+ - Trust ViewThatFits to pick the right layout
1165
+ </anti_pattern>
1166
+
1167
+ </anti_patterns>
1168
+
1169
+ **Sources:**
1170
+ Research for this reference included:
1171
+ - [SwiftUI Layout System (kean.blog)](https://kean.blog/post/swiftui-layout-system)
1172
+ - [Custom Layouts in SwiftUI (Medium)](https://medium.com/@wesleymatlock/custom-layouts-in-swiftui-a-deep-dive-into-the-layout-protocol-5edc691cd4fb)
1173
+ - [A guide to the SwiftUI layout system (Swift by Sundell)](https://www.swiftbysundell.com/articles/swiftui-layout-system-guide-part-1/)
1174
+ - [Creating custom layouts with Layout protocol (Hacking with Swift)](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-a-custom-layout-using-the-layout-protocol)
1175
+ - [Apple Developer: Composing custom layouts with SwiftUI](https://developer.apple.com/documentation/swiftui/composing_custom_layouts_with_swiftui)
1176
+ - [Custom Layout in SwiftUI (Sarunw)](https://sarunw.com/posts/swiftui-custom-layout/)
1177
+ - [GeometryReader - Blessing or Curse? (fatbobman)](https://fatbobman.com/en/posts/geometryreader-blessing-or-curse/)
1178
+ - [Mastering GeometryReader in SwiftUI (DEV Community)](https://dev.to/qmshahzad/mastering-geometryreader-in-swiftui-from-basics-to-advanced-layout-control-5akk)
1179
+ - [SwiftUI Grid, LazyVGrid, LazyHGrid (avanderlee)](https://www.avanderlee.com/swiftui/grid-lazyvgrid-lazyhgrid-gridviews/)
1180
+ - [Tuning Lazy Stacks and Grids Performance Guide (Medium)](https://medium.com/@wesleymatlock/tuning-lazy-stacks-and-grids-in-swiftui-a-performance-guide-2fb10786f76a)
1181
+ - [containerRelativeFrame Modifier (fatbobman)](https://fatbobman.com/en/posts/mastering-the-containerrelativeframe-modifier-in-swiftui/)
1182
+ - [ViewThatFits adaptive layout (Hacking with Swift)](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-an-adaptive-layout-with-viewthatfits)
1183
+ - [Mastering ViewThatFits (fatbobman)](https://fatbobman.com/en/posts/mastering-viewthatfits/)
1184
+ - [Alignment Guides in SwiftUI (The SwiftUI Lab)](https://swiftui-lab.com/alignment-guides/)
1185
+ - [Managing safe area in SwiftUI (Swift with Majid)](https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/)
1186
+ - [Mastering Safe Area in SwiftUI (fatbobman)](https://fatbobman.com/en/posts/safearea/)