claudecode-omc 5.6.7 → 5.6.8
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.
- package/.local/skills/THIRD_PARTY_LICENSES/AvdLee-SwiftUI-Agent-Skill.LICENSE +21 -0
- package/.local/skills/THIRD_PARTY_LICENSES/Dimillian-Skills.LICENSE +21 -0
- package/.local/skills/THIRD_PARTY_LICENSES/README.md +36 -0
- package/.local/skills/THIRD_PARTY_LICENSES/twostraws-swiftui-agent-skill.LICENSE +21 -0
- package/.local/skills/ios-debugger-agent/SKILL.md +51 -0
- package/.local/skills/ios-debugger-agent/agents/openai.yaml +4 -0
- package/.local/skills/swift-concurrency-expert/SKILL.md +105 -0
- package/.local/skills/swift-concurrency-expert/agents/openai.yaml +4 -0
- package/.local/skills/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
- package/.local/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
- package/.local/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
- package/.local/skills/swiftui-expert-skill/SKILL.md +162 -0
- package/.local/skills/swiftui-expert-skill/references/accessibility-patterns.md +215 -0
- package/.local/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
- package/.local/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
- package/.local/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
- package/.local/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
- package/.local/skills/swiftui-expert-skill/references/charts.md +602 -0
- package/.local/skills/swiftui-expert-skill/references/focus-patterns.md +299 -0
- package/.local/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
- package/.local/skills/swiftui-expert-skill/references/latest-apis.md +488 -0
- package/.local/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
- package/.local/skills/swiftui-expert-skill/references/liquid-glass.md +423 -0
- package/.local/skills/swiftui-expert-skill/references/list-patterns.md +446 -0
- package/.local/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
- package/.local/skills/swiftui-expert-skill/references/macos-views.md +357 -0
- package/.local/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
- package/.local/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
- package/.local/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
- package/.local/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
- package/.local/skills/swiftui-expert-skill/references/state-management.md +388 -0
- package/.local/skills/swiftui-expert-skill/references/text-patterns.md +32 -0
- package/.local/skills/swiftui-expert-skill/references/trace-analysis.md +295 -0
- package/.local/skills/swiftui-expert-skill/references/trace-recording.md +134 -0
- package/.local/skills/swiftui-expert-skill/references/view-structure.md +780 -0
- package/.local/skills/swiftui-expert-skill/scripts/__pycache__/analyze_trace.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/__pycache__/record_trace.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/analyze_trace.py +301 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__init__.py +1 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/__init__.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/causes.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/correlate.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/events.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hangs.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hitches.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/summary.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/swiftui.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/time_profiler.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xctrace.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xml_utils.cpython-313.pyc +0 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/causes.py +187 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/correlate.py +179 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/events.py +291 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hangs.py +108 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hitches.py +145 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/summary.py +243 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/swiftui.py +195 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/time_profiler.py +135 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +117 -0
- package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +224 -0
- package/.local/skills/swiftui-expert-skill/scripts/record_trace.py +252 -0
- package/.local/skills/swiftui-liquid-glass/SKILL.md +90 -0
- package/.local/skills/swiftui-liquid-glass/agents/openai.yaml +4 -0
- package/.local/skills/swiftui-liquid-glass/references/liquid-glass.md +280 -0
- package/.local/skills/swiftui-performance-audit/SKILL.md +106 -0
- package/.local/skills/swiftui-performance-audit/agents/openai.yaml +4 -0
- package/.local/skills/swiftui-performance-audit/references/code-smells.md +150 -0
- package/.local/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
- package/.local/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
- package/.local/skills/swiftui-performance-audit/references/profiling-intake.md +44 -0
- package/.local/skills/swiftui-performance-audit/references/report-template.md +47 -0
- package/.local/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
- package/.local/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
- package/.local/skills/swiftui-pro/SKILL.md +108 -0
- package/.local/skills/swiftui-pro/agents/openai.yaml +10 -0
- package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
- package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
- package/.local/skills/swiftui-pro/references/accessibility.md +13 -0
- package/.local/skills/swiftui-pro/references/api.md +39 -0
- package/.local/skills/swiftui-pro/references/data.md +43 -0
- package/.local/skills/swiftui-pro/references/design.md +32 -0
- package/.local/skills/swiftui-pro/references/hygiene.md +9 -0
- package/.local/skills/swiftui-pro/references/navigation.md +14 -0
- package/.local/skills/swiftui-pro/references/performance.md +46 -0
- package/.local/skills/swiftui-pro/references/swift.md +56 -0
- package/.local/skills/swiftui-pro/references/views.md +36 -0
- package/.local/skills/swiftui-ui-patterns/SKILL.md +95 -0
- package/.local/skills/swiftui-ui-patterns/agents/openai.yaml +4 -0
- package/.local/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
- package/.local/skills/swiftui-ui-patterns/references/async-state.md +96 -0
- package/.local/skills/swiftui-ui-patterns/references/components-index.md +50 -0
- package/.local/skills/swiftui-ui-patterns/references/controls.md +57 -0
- package/.local/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
- package/.local/skills/swiftui-ui-patterns/references/focus.md +90 -0
- package/.local/skills/swiftui-ui-patterns/references/form.md +97 -0
- package/.local/skills/swiftui-ui-patterns/references/grids.md +71 -0
- package/.local/skills/swiftui-ui-patterns/references/haptics.md +71 -0
- package/.local/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
- package/.local/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
- package/.local/skills/swiftui-ui-patterns/references/list.md +86 -0
- package/.local/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
- package/.local/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
- package/.local/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
- package/.local/skills/swiftui-ui-patterns/references/media.md +73 -0
- package/.local/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
- package/.local/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
- package/.local/skills/swiftui-ui-patterns/references/overlay.md +45 -0
- package/.local/skills/swiftui-ui-patterns/references/performance.md +62 -0
- package/.local/skills/swiftui-ui-patterns/references/previews.md +48 -0
- package/.local/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
- package/.local/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
- package/.local/skills/swiftui-ui-patterns/references/searchable.md +71 -0
- package/.local/skills/swiftui-ui-patterns/references/sheets.md +155 -0
- package/.local/skills/swiftui-ui-patterns/references/split-views.md +72 -0
- package/.local/skills/swiftui-ui-patterns/references/tabview.md +114 -0
- package/.local/skills/swiftui-ui-patterns/references/theming.md +71 -0
- package/.local/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
- package/.local/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
- package/.local/skills/swiftui-view-refactor/SKILL.md +202 -0
- package/.local/skills/swiftui-view-refactor/agents/openai.yaml +4 -0
- package/.local/skills/swiftui-view-refactor/references/mv-patterns.md +161 -0
- package/bundled/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# macOS Window & Toolbar Styling Reference
|
|
2
|
+
|
|
3
|
+
> Window configuration, toolbar styles, sizing, positioning, and navigation patterns specific to macOS SwiftUI apps.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Quick Lookup Table](#quick-lookup-table)
|
|
8
|
+
- [Toolbar Styles](#toolbar-styles)
|
|
9
|
+
- [Window Style](#window-style)
|
|
10
|
+
- [Window Sizing](#window-sizing)
|
|
11
|
+
- [MenuBarExtra Style (macOS-only)](#menubarextra-style-macos-only)
|
|
12
|
+
- [Navigation Layout (macOS behavior)](#navigation-layout-macos-behavior)
|
|
13
|
+
- [Commands & Keyboard](#commands--keyboard)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Lookup Table
|
|
19
|
+
|
|
20
|
+
| API | Availability | macOS-Only? | Usage |
|
|
21
|
+
|-----|-------------|:-----------:|-------|
|
|
22
|
+
| `windowToolbarStyle(_:)` | macOS 11.0+ | Yes | Sets toolbar style: `.unified`, `.unifiedCompact`, `.expanded` |
|
|
23
|
+
| `windowStyle(_:)` | macOS 11.0+ | No | Supports `.hiddenTitleBar` for chromeless windows |
|
|
24
|
+
| `windowResizability(_:)` | macOS 13.0+ | No | Controls resize handle and green zoom button behavior |
|
|
25
|
+
| `defaultSize(width:height:)` | macOS 13.0+ | No | Initial frame size when user creates a new window |
|
|
26
|
+
| `defaultPosition(_:)` | macOS 13.0+ | No | Initial window position on screen |
|
|
27
|
+
| `windowIdealPlacement(_:)` | macOS 15.0+ | No | Closure with display geometry for precise window positioning |
|
|
28
|
+
| `menuBarExtraStyle(_:)` | macOS 13.0+ | Yes | Sets MenuBarExtra to `.menu` or `.window` style |
|
|
29
|
+
| `NavigationSplitView` | macOS 13.0+ | No | Columns always visible side-by-side on macOS; translucent sidebar |
|
|
30
|
+
| `Inspector` | macOS 14.0+ | No | Trailing-edge sidebar panel; resizable by dragging |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Toolbar Styles
|
|
35
|
+
|
|
36
|
+
### windowToolbarStyle (macOS-only)
|
|
37
|
+
|
|
38
|
+
Controls how the toolbar and title bar are displayed. Applied to a scene.
|
|
39
|
+
|
|
40
|
+
```swift
|
|
41
|
+
@main
|
|
42
|
+
struct MyApp: App {
|
|
43
|
+
var body: some Scene {
|
|
44
|
+
WindowGroup {
|
|
45
|
+
ContentView()
|
|
46
|
+
}
|
|
47
|
+
// Title bar and toolbar in a single row
|
|
48
|
+
.windowToolbarStyle(.unified)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Available styles:**
|
|
54
|
+
|
|
55
|
+
| Style | Description |
|
|
56
|
+
|-------|-------------|
|
|
57
|
+
| `.automatic` | System default |
|
|
58
|
+
| `.unified` | Title bar and toolbar in a single combined row |
|
|
59
|
+
| `.unifiedCompact` | Same as unified but with reduced vertical height |
|
|
60
|
+
| `.expanded` | Title bar displayed above the toolbar (more toolbar space) |
|
|
61
|
+
|
|
62
|
+
```swift
|
|
63
|
+
// Unified compact — minimal chrome
|
|
64
|
+
.windowToolbarStyle(.unifiedCompact)
|
|
65
|
+
|
|
66
|
+
// Expanded — title bar above toolbar
|
|
67
|
+
.windowToolbarStyle(.expanded)
|
|
68
|
+
|
|
69
|
+
// Unified with title hidden
|
|
70
|
+
.windowToolbarStyle(.unified(showsTitle: false))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Toolbar content
|
|
74
|
+
|
|
75
|
+
```swift
|
|
76
|
+
struct ContentView: View {
|
|
77
|
+
@State private var searchText = ""
|
|
78
|
+
|
|
79
|
+
var body: some View {
|
|
80
|
+
NavigationSplitView {
|
|
81
|
+
SidebarView()
|
|
82
|
+
} detail: {
|
|
83
|
+
DetailView()
|
|
84
|
+
}
|
|
85
|
+
.toolbar {
|
|
86
|
+
ToolbarItem(placement: .automatic) {
|
|
87
|
+
Button(action: addItem) {
|
|
88
|
+
Label("Add", systemImage: "plus")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
.searchable(text: $searchText, placement: .sidebar)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Window Style
|
|
100
|
+
|
|
101
|
+
### windowStyle
|
|
102
|
+
|
|
103
|
+
Set the visual style of a window. Use `.hiddenTitleBar` for chromeless, immersive windows.
|
|
104
|
+
|
|
105
|
+
```swift
|
|
106
|
+
// Standard title bar (default)
|
|
107
|
+
WindowGroup {
|
|
108
|
+
ContentView()
|
|
109
|
+
}
|
|
110
|
+
.windowStyle(.titleBar)
|
|
111
|
+
|
|
112
|
+
// Hidden title bar — chromeless window
|
|
113
|
+
WindowGroup {
|
|
114
|
+
ContentView()
|
|
115
|
+
}
|
|
116
|
+
.windowStyle(.hiddenTitleBar)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> **Use case:** `.hiddenTitleBar` is useful for media players, custom-chrome apps, or immersive experiences where the standard title bar is unwanted.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Window Sizing
|
|
124
|
+
|
|
125
|
+
### windowResizability, defaultSize, defaultPosition
|
|
126
|
+
|
|
127
|
+
These modifiers work together to configure window sizing and placement:
|
|
128
|
+
|
|
129
|
+
```swift
|
|
130
|
+
WindowGroup {
|
|
131
|
+
ContentView()
|
|
132
|
+
.frame(minWidth: 600, minHeight: 400)
|
|
133
|
+
}
|
|
134
|
+
.defaultSize(width: 900, height: 600)
|
|
135
|
+
.defaultPosition(.center)
|
|
136
|
+
.windowResizability(.contentMinSize)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**`windowResizability` options:**
|
|
140
|
+
|
|
141
|
+
| Value | Behavior |
|
|
142
|
+
|-------|----------|
|
|
143
|
+
| `.automatic` | System decides resize behavior |
|
|
144
|
+
| `.contentSize` | Fixed to content size; no user resize; zoom button disabled |
|
|
145
|
+
| `.contentMinSize` | Resizable with minimum based on content's `minWidth`/`minHeight` |
|
|
146
|
+
|
|
147
|
+
**`defaultPosition` options:** `.center`, `.topLeading`, `.top`, `.topTrailing`, `.leading`, `.trailing`, `.bottomLeading`, `.bottom`, `.bottomTrailing`
|
|
148
|
+
|
|
149
|
+
**Guidelines:**
|
|
150
|
+
- Set `minWidth`/`minHeight` via `.frame()` on content, enforce with `.contentMinSize`
|
|
151
|
+
- Use `.defaultSize()` for initial dimensions (larger than minimums)
|
|
152
|
+
- `defaultSize` also accepts `CGSize`
|
|
153
|
+
|
|
154
|
+
### windowIdealPlacement (macOS 15.0+)
|
|
155
|
+
|
|
156
|
+
For precise programmatic positioning, use a closure with display geometry:
|
|
157
|
+
|
|
158
|
+
```swift
|
|
159
|
+
.windowIdealPlacement { context in
|
|
160
|
+
let screen = context.defaultDisplay.visibleArea
|
|
161
|
+
return WindowPlacement(x: screen.midX, y: screen.midY,
|
|
162
|
+
width: screen.width / 2, height: screen.height)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## MenuBarExtra Style (macOS-only)
|
|
169
|
+
|
|
170
|
+
Choose between dropdown menu and popover panel for `MenuBarExtra`.
|
|
171
|
+
|
|
172
|
+
```swift
|
|
173
|
+
// Dropdown menu (default)
|
|
174
|
+
MenuBarExtra("Status", systemImage: "chart.bar") {
|
|
175
|
+
Button("Action") { /* ... */ }
|
|
176
|
+
}
|
|
177
|
+
.menuBarExtraStyle(.menu)
|
|
178
|
+
|
|
179
|
+
// Popover panel with custom SwiftUI content
|
|
180
|
+
MenuBarExtra("Status", systemImage: "chart.bar") {
|
|
181
|
+
DashboardView()
|
|
182
|
+
}
|
|
183
|
+
.menuBarExtraStyle(.window)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Navigation Layout (macOS behavior)
|
|
189
|
+
|
|
190
|
+
### NavigationSplitView
|
|
191
|
+
|
|
192
|
+
On macOS, `NavigationSplitView` displays columns side-by-side (never overlaid). The sidebar gets a translucent material background. Columns support variable-width resizing by the user.
|
|
193
|
+
|
|
194
|
+
```swift
|
|
195
|
+
NavigationSplitView {
|
|
196
|
+
List(items, selection: $selectedId) { item in
|
|
197
|
+
Text(item.name)
|
|
198
|
+
}
|
|
199
|
+
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
|
|
200
|
+
} detail: {
|
|
201
|
+
DetailView(id: selectedId)
|
|
202
|
+
}
|
|
203
|
+
.navigationSplitViewStyle(.balanced)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Use the three-column variant (`sidebar` / `content` / `detail`) for master-detail-detail layouts. Customize column widths with `.navigationSplitViewColumnWidth(min:ideal:max:)`.
|
|
207
|
+
|
|
208
|
+
### Inspector (macOS 14.0+)
|
|
209
|
+
|
|
210
|
+
A trailing-edge panel for supplementary information. On macOS, it appears as a sidebar-style panel that can be resized by dragging its edge.
|
|
211
|
+
|
|
212
|
+
```swift
|
|
213
|
+
struct ContentView: View {
|
|
214
|
+
@State private var showInspector = false
|
|
215
|
+
|
|
216
|
+
var body: some View {
|
|
217
|
+
MainContent()
|
|
218
|
+
.inspector(isPresented: $showInspector) {
|
|
219
|
+
InspectorView()
|
|
220
|
+
.inspectorColumnWidth(min: 200, ideal: 250, max: 400)
|
|
221
|
+
}
|
|
222
|
+
.toolbar {
|
|
223
|
+
ToolbarItem {
|
|
224
|
+
Button {
|
|
225
|
+
showInspector.toggle()
|
|
226
|
+
} label: {
|
|
227
|
+
Label("Inspector", systemImage: "info.circle")
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Commands & Keyboard
|
|
238
|
+
|
|
239
|
+
### Commands, CommandGroup, CommandMenu
|
|
240
|
+
|
|
241
|
+
Define menu bar commands. On macOS, these populate the menu bar directly. On iOS, they create key commands.
|
|
242
|
+
|
|
243
|
+
```swift
|
|
244
|
+
.commands {
|
|
245
|
+
CommandMenu("Tools") {
|
|
246
|
+
Button("Run Analysis") { /* ... */ }
|
|
247
|
+
.keyboardShortcut("r", modifiers: [.command, .shift])
|
|
248
|
+
}
|
|
249
|
+
CommandGroup(after: .newItem) {
|
|
250
|
+
Button("New From Template...") { /* ... */ }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**`CommandGroup` placement options:** `.replacing(_:)` replaces a system group, `.before(_:)` / `.after(_:)` inserts adjacent to it. Common placements: `.newItem`, `.saveItem`, `.help`, `.toolbar`, `.sidebar`.
|
|
256
|
+
|
|
257
|
+
### KeyboardShortcut
|
|
258
|
+
|
|
259
|
+
On macOS, shortcuts are displayed alongside menu items and in button tooltips on hover.
|
|
260
|
+
|
|
261
|
+
```swift
|
|
262
|
+
Button("Save") {
|
|
263
|
+
save()
|
|
264
|
+
}
|
|
265
|
+
.keyboardShortcut("s", modifiers: .command)
|
|
266
|
+
|
|
267
|
+
Button("Delete") {
|
|
268
|
+
delete()
|
|
269
|
+
}
|
|
270
|
+
.keyboardShortcut(.delete, modifiers: .command)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### openWindow
|
|
274
|
+
|
|
275
|
+
Programmatically open a window. If the target window is already open, brings it to the front.
|
|
276
|
+
|
|
277
|
+
```swift
|
|
278
|
+
struct ToolbarActions: View {
|
|
279
|
+
@Environment(\.openWindow) private var openWindow
|
|
280
|
+
|
|
281
|
+
var body: some View {
|
|
282
|
+
Button("Connection Doctor") {
|
|
283
|
+
openWindow(id: "connection-doctor")
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
Button("Show Message") {
|
|
287
|
+
openWindow(value: message.id) // Type-matched to WindowGroup
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Best Practices
|
|
296
|
+
|
|
297
|
+
- **Use `.unified` or `.unifiedCompact`** for most apps — `.expanded` only when you need many toolbar items
|
|
298
|
+
- **Set min frame sizes on content** and use `.windowResizability(.contentMinSize)` to enforce them
|
|
299
|
+
- **Always provide `defaultSize`** so new windows start at a reasonable size
|
|
300
|
+
- **Use `NavigationSplitView`** for sidebar navigation — not `HSplitView`
|
|
301
|
+
- **Use `Inspector`** for supplementary panels — it integrates with the toolbar automatically
|
|
302
|
+
- **Define `Commands`** for all repeatable actions — users expect keyboard shortcuts on macOS
|
|
303
|
+
- **Use `#if os(macOS)`** to wrap macOS-only window configuration in multiplatform projects
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# SwiftUI Performance Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Performance Optimization](#performance-optimization)
|
|
6
|
+
- [Anti-Patterns](#anti-patterns)
|
|
7
|
+
- [Summary Checklist](#summary-checklist)
|
|
8
|
+
|
|
9
|
+
## Performance Optimization
|
|
10
|
+
|
|
11
|
+
### 1. Avoid Redundant State Updates
|
|
12
|
+
|
|
13
|
+
SwiftUI doesn't compare values before triggering updates:
|
|
14
|
+
|
|
15
|
+
```swift
|
|
16
|
+
// BAD - triggers update even if value unchanged
|
|
17
|
+
.onReceive(publisher) { value in
|
|
18
|
+
self.currentValue = value // Always triggers body re-evaluation
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// GOOD - only update when different
|
|
22
|
+
.onReceive(publisher) { value in
|
|
23
|
+
if self.currentValue != value {
|
|
24
|
+
self.currentValue = value
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Optimize Hot Paths
|
|
30
|
+
|
|
31
|
+
Hot paths are frequently executed code (scroll handlers, animations, gestures):
|
|
32
|
+
|
|
33
|
+
```swift
|
|
34
|
+
// BAD - updates state on every scroll position change
|
|
35
|
+
.onPreferenceChange(ScrollOffsetKey.self) { offset in
|
|
36
|
+
shouldShowTitle = offset.y <= -32 // Fires constantly during scroll!
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// GOOD - only update when threshold crossed
|
|
40
|
+
.onPreferenceChange(ScrollOffsetKey.self) { offset in
|
|
41
|
+
let shouldShow = offset.y <= -32
|
|
42
|
+
if shouldShow != shouldShowTitle {
|
|
43
|
+
shouldShowTitle = shouldShow // Fires only when crossing threshold
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Pass Only What Views Need
|
|
49
|
+
|
|
50
|
+
**Avoid passing large "config" or "context" objects.** Pass only the specific values each view needs.
|
|
51
|
+
|
|
52
|
+
```swift
|
|
53
|
+
// Good - pass specific values
|
|
54
|
+
ThemeSelector(theme: config.theme)
|
|
55
|
+
FontSizeSlider(fontSize: config.fontSize)
|
|
56
|
+
|
|
57
|
+
// Avoid - passing entire config (creates broad dependency)
|
|
58
|
+
ThemeSelector(config: config) // Notified of ALL config changes
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
With `ObservableObject`, any `@Published` change triggers all observers. With `@Observable`, views update only when accessed properties change, but passing entire objects still creates broader dependencies than necessary.
|
|
62
|
+
|
|
63
|
+
### 4. Use Equatable Views
|
|
64
|
+
|
|
65
|
+
For views with expensive bodies, conform to `Equatable`:
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
struct ExpensiveView: View, Equatable {
|
|
69
|
+
let data: SomeData
|
|
70
|
+
|
|
71
|
+
static func == (lhs: Self, rhs: Self) -> Bool {
|
|
72
|
+
lhs.data.id == rhs.data.id // Custom equality check
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
var body: some View {
|
|
76
|
+
// Expensive computation
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Usage
|
|
81
|
+
ExpensiveView(data: data)
|
|
82
|
+
.equatable() // Use custom equality
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Caution**: If you add new state or dependencies to your view, remember to update your `==` function!
|
|
86
|
+
|
|
87
|
+
### 5. POD Views for Fast Diffing
|
|
88
|
+
|
|
89
|
+
**POD (Plain Old Data) views use `memcmp` for fastest diffing.** A view is POD if it only contains simple value types and no property wrappers.
|
|
90
|
+
|
|
91
|
+
```swift
|
|
92
|
+
// POD view - fastest diffing
|
|
93
|
+
struct FastView: View {
|
|
94
|
+
let title: String
|
|
95
|
+
let count: Int
|
|
96
|
+
|
|
97
|
+
var body: some View {
|
|
98
|
+
Text("\(title): \(count)")
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Non-POD view - uses reflection or custom equality
|
|
103
|
+
struct SlowerView: View {
|
|
104
|
+
let title: String
|
|
105
|
+
@State private var isExpanded = false // Property wrapper makes it non-POD
|
|
106
|
+
|
|
107
|
+
var body: some View {
|
|
108
|
+
Text(title)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Advanced Pattern**: Wrap expensive non-POD views in POD parent views:
|
|
114
|
+
|
|
115
|
+
```swift
|
|
116
|
+
// POD wrapper for fast diffing
|
|
117
|
+
struct ExpensiveView: View {
|
|
118
|
+
let value: Int
|
|
119
|
+
|
|
120
|
+
var body: some View {
|
|
121
|
+
ExpensiveViewInternal(value: value)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Internal view with state
|
|
126
|
+
private struct ExpensiveViewInternal: View {
|
|
127
|
+
let value: Int
|
|
128
|
+
@State private var item: Item?
|
|
129
|
+
|
|
130
|
+
var body: some View {
|
|
131
|
+
// Expensive rendering
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Why**: The POD parent uses fast `memcmp` comparison. Only when `value` changes does the internal view get diffed.
|
|
137
|
+
|
|
138
|
+
### 6. Lazy Loading
|
|
139
|
+
|
|
140
|
+
Use lazy containers for large collections:
|
|
141
|
+
|
|
142
|
+
```swift
|
|
143
|
+
// BAD - creates all views immediately
|
|
144
|
+
ScrollView {
|
|
145
|
+
VStack {
|
|
146
|
+
ForEach(items) { item in
|
|
147
|
+
ExpensiveRow(item: item)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// GOOD - creates views on demand
|
|
153
|
+
ScrollView {
|
|
154
|
+
LazyVStack {
|
|
155
|
+
ForEach(items) { item in
|
|
156
|
+
ExpensiveRow(item: item)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**iOS 26+ note**: Nested scroll views containing lazy stacks now automatically defer loading their children until they are about to appear, matching the behavior of top-level lazy stacks. This benefits patterns like horizontal photo carousels inside a vertical scroll view.
|
|
163
|
+
|
|
164
|
+
> Source: "What's new in SwiftUI" (WWDC25, session 256)
|
|
165
|
+
|
|
166
|
+
### 7. Task Cancellation
|
|
167
|
+
|
|
168
|
+
Cancel async work when view disappears:
|
|
169
|
+
|
|
170
|
+
```swift
|
|
171
|
+
struct DataView: View {
|
|
172
|
+
@State private var data: [Item] = []
|
|
173
|
+
|
|
174
|
+
var body: some View {
|
|
175
|
+
List(data) { item in
|
|
176
|
+
Text(item.name)
|
|
177
|
+
}
|
|
178
|
+
.task {
|
|
179
|
+
// Automatically cancelled when view disappears
|
|
180
|
+
data = await fetchData()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 8. Debug View Updates
|
|
187
|
+
|
|
188
|
+
**Use `Self._printChanges()` or `Self._logChanges()` to debug unexpected view updates.**
|
|
189
|
+
|
|
190
|
+
```swift
|
|
191
|
+
struct DebugView: View {
|
|
192
|
+
@State private var count = 0
|
|
193
|
+
@State private var name = ""
|
|
194
|
+
|
|
195
|
+
var body: some View {
|
|
196
|
+
#if DEBUG
|
|
197
|
+
let _ = Self._logChanges() // Xcode 15.1+: logs to com.apple.SwiftUI subsystem
|
|
198
|
+
#endif
|
|
199
|
+
|
|
200
|
+
VStack {
|
|
201
|
+
Text("Count: \(count)")
|
|
202
|
+
Text("Name: \(name)")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- `Self._printChanges()`: Prints which properties changed to standard output.
|
|
209
|
+
- `Self._logChanges()` (iOS 17+): Logs to the `com.apple.SwiftUI` subsystem with category "Changed Body Properties", using `os_log` for structured output.
|
|
210
|
+
|
|
211
|
+
Both print `@self` when the view value itself changed and `@identity` when the view's persistent data was recycled.
|
|
212
|
+
|
|
213
|
+
**Why**: This helps identify which state changes are causing view updates. Isolating redraw triggers into single-responsibility subviews is often the fix -- extracting a subview means SwiftUI can skip its body when its inputs haven't changed.
|
|
214
|
+
|
|
215
|
+
### 9. Eliminate Unnecessary Dependencies
|
|
216
|
+
|
|
217
|
+
**Narrow state scope to reduce update fan-out.** Instead of passing an entire `@Observable` model to a row view (which creates a dependency on all accessed properties), pass only the specific values the view needs as `let` properties.
|
|
218
|
+
|
|
219
|
+
```swift
|
|
220
|
+
// Bad - broad dependency on entire model
|
|
221
|
+
struct ItemRow: View {
|
|
222
|
+
@Environment(AppModel.self) private var model
|
|
223
|
+
let item: Item
|
|
224
|
+
var body: some View { Text(item.name).foregroundStyle(model.theme.primaryColor) }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Good - narrow dependency
|
|
228
|
+
struct ItemRow: View {
|
|
229
|
+
let item: Item
|
|
230
|
+
let themeColor: Color
|
|
231
|
+
var body: some View { Text(item.name).foregroundStyle(themeColor) }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Avoid storing frequently-changing values in the environment.** Even when a view doesn't read the changed key, SwiftUI still checks all environment readers. This cost adds up with many views and frequent updates (geometry values, timers).
|
|
236
|
+
|
|
237
|
+
> Source: "Optimize SwiftUI performance with Instruments" (WWDC25, session 306)
|
|
238
|
+
|
|
239
|
+
### 10. @Observable Dependency Granularity
|
|
240
|
+
|
|
241
|
+
**Consider per-item `@Observable` state holders (one per row/item) to narrow update scope.** When multiple list items share a dependency on the same `@Observable` array, changing one element causes all items to re-evaluate their bodies.
|
|
242
|
+
|
|
243
|
+
```swift
|
|
244
|
+
// BAD - all item views depend on the full favorites array
|
|
245
|
+
@Observable
|
|
246
|
+
class ModelData {
|
|
247
|
+
var favorites: [Landmark] = []
|
|
248
|
+
|
|
249
|
+
func isFavorite(_ landmark: Landmark) -> Bool {
|
|
250
|
+
favorites.contains(landmark)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
struct LandmarkRow: View {
|
|
255
|
+
let landmark: Landmark
|
|
256
|
+
@Environment(ModelData.self) private var model
|
|
257
|
+
|
|
258
|
+
var body: some View {
|
|
259
|
+
HStack {
|
|
260
|
+
Text(landmark.name)
|
|
261
|
+
if model.isFavorite(landmark) {
|
|
262
|
+
Image(systemName: "heart.fill")
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// GOOD - each item has its own observable view model
|
|
269
|
+
@Observable
|
|
270
|
+
class LandmarkViewModel {
|
|
271
|
+
var isFavorite: Bool = false
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
struct LandmarkRow: View {
|
|
275
|
+
let landmark: Landmark
|
|
276
|
+
let viewModel: LandmarkViewModel
|
|
277
|
+
|
|
278
|
+
var body: some View {
|
|
279
|
+
HStack {
|
|
280
|
+
Text(landmark.name)
|
|
281
|
+
if viewModel.isFavorite {
|
|
282
|
+
Image(systemName: "heart.fill")
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Why**: With the bad pattern, toggling one favorite marks the entire array as changed, causing every `LandmarkRow` to re-run its body. With per-item view models, only the toggled item's body runs.
|
|
290
|
+
|
|
291
|
+
> Source: "Optimize SwiftUI performance with Instruments" (WWDC25, session 306)
|
|
292
|
+
|
|
293
|
+
### 11. Off-Main-Thread Closures
|
|
294
|
+
|
|
295
|
+
**SwiftUI may call certain closures on a background thread for performance.** These closures must be `Sendable` and should avoid accessing `@MainActor`-isolated state directly. Instead, capture needed values in the closure's capture list.
|
|
296
|
+
|
|
297
|
+
Closures that may run off the main thread:
|
|
298
|
+
- `Shape.path(in:)`
|
|
299
|
+
- `visualEffect` closure
|
|
300
|
+
- `Layout` protocol methods
|
|
301
|
+
- `onGeometryChange` transform closure
|
|
302
|
+
|
|
303
|
+
```swift
|
|
304
|
+
// BAD - accessing @MainActor state directly
|
|
305
|
+
.visualEffect { content, geometry in
|
|
306
|
+
content.blur(radius: self.pulse ? 5 : 0) // Compiler error: @MainActor isolated
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// GOOD - capture the value
|
|
310
|
+
.visualEffect { [pulse] content, geometry in
|
|
311
|
+
content.blur(radius: pulse ? 5 : 0)
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> Source: "Explore concurrency in SwiftUI" (WWDC25, session 266)
|
|
316
|
+
|
|
317
|
+
### 12. Common Performance Issues
|
|
318
|
+
|
|
319
|
+
**Be aware of common performance bottlenecks in SwiftUI:**
|
|
320
|
+
|
|
321
|
+
- View invalidation storms from broad state changes
|
|
322
|
+
- Unstable identity in lists causing excessive diffing
|
|
323
|
+
- Heavy work in `body` (formatting, sorting, image decoding)
|
|
324
|
+
- Layout thrash from deep stacks or preference chains
|
|
325
|
+
|
|
326
|
+
**When performance issues arise**, suggest the user profile with Instruments (SwiftUI template) to identify specific bottlenecks.
|
|
327
|
+
|
|
328
|
+
## Anti-Patterns
|
|
329
|
+
|
|
330
|
+
### 1. Creating Objects in Body
|
|
331
|
+
|
|
332
|
+
```swift
|
|
333
|
+
// BAD - creates new formatter every body call
|
|
334
|
+
var body: some View {
|
|
335
|
+
let formatter = DateFormatter()
|
|
336
|
+
formatter.dateStyle = .long
|
|
337
|
+
return Text(formatter.string(from: date))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// GOOD - static or stored formatter
|
|
341
|
+
private static let dateFormatter: DateFormatter = {
|
|
342
|
+
let f = DateFormatter()
|
|
343
|
+
f.dateStyle = .long
|
|
344
|
+
return f
|
|
345
|
+
}()
|
|
346
|
+
|
|
347
|
+
var body: some View {
|
|
348
|
+
Text(Self.dateFormatter.string(from: date))
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 2. Heavy Computation in Body
|
|
353
|
+
|
|
354
|
+
**Keep view body simple and pure.** Avoid side effects, dispatching, or complex logic.
|
|
355
|
+
|
|
356
|
+
```swift
|
|
357
|
+
// BAD - sorts array every body call
|
|
358
|
+
var body: some View {
|
|
359
|
+
List(items.sorted { $0.name < $1.name }) { item in Text(item.name) }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// GOOD - compute once, update via onChange or a computed property in the model
|
|
363
|
+
@State private var sortedItems: [Item] = []
|
|
364
|
+
|
|
365
|
+
var body: some View {
|
|
366
|
+
List(sortedItems) { item in Text(item.name) }
|
|
367
|
+
.onChange(of: items) { _, newItems in
|
|
368
|
+
sortedItems = newItems.sorted { $0.name < $1.name }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Move sorting, filtering, and formatting into models or computed properties. The `body` should be a pure structural representation of state.
|
|
374
|
+
|
|
375
|
+
### 3. Unnecessary State
|
|
376
|
+
|
|
377
|
+
```swift
|
|
378
|
+
// BAD - derived state stored separately
|
|
379
|
+
@State private var items: [Item] = []
|
|
380
|
+
@State private var itemCount: Int = 0 // Unnecessary!
|
|
381
|
+
|
|
382
|
+
// GOOD - compute derived values
|
|
383
|
+
@State private var items: [Item] = []
|
|
384
|
+
|
|
385
|
+
var itemCount: Int { items.count } // Computed property
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Summary Checklist
|
|
389
|
+
|
|
390
|
+
- [ ] State updates check for value changes before assigning
|
|
391
|
+
- [ ] Hot paths minimize state updates
|
|
392
|
+
- [ ] Pass only needed values to views (avoid large config objects)
|
|
393
|
+
- [ ] Large lists use `LazyVStack`/`LazyHStack`
|
|
394
|
+
- [ ] No object creation in `body`
|
|
395
|
+
- [ ] Heavy computation moved out of `body`
|
|
396
|
+
- [ ] Body kept simple and pure (no side effects)
|
|
397
|
+
- [ ] Derived state computed, not stored
|
|
398
|
+
- [ ] Use `Self._logChanges()` or `Self._printChanges()` to debug unexpected updates
|
|
399
|
+
- [ ] Equatable conformance for expensive views (when appropriate)
|
|
400
|
+
- [ ] Consider POD view wrappers for advanced optimization
|
|
401
|
+
- [ ] Consider using granular @Observable dependencies for list items (smaller observable units per row when it measurably reduces updates)
|
|
402
|
+
- [ ] Frequently-changing values not stored in the environment
|
|
403
|
+
- [ ] Sendable closures (Shape, visualEffect, Layout) capture values instead of accessing @MainActor state
|