claudecode-omc 5.6.6 → 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/h5-to-swiftui/SKILL.md +201 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/README.md +176 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/index.html +52 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/style.css +133 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Package.swift +26 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Sources/CalibrationScreen/CalibrationScreen.swift +142 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Package.swift +32 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Sources/CalibrationScreenDivergent/CalibrationScreenDivergent.swift +122 -0
- package/.local/skills/h5-to-swiftui/assets/calibration/tokens.json +42 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/index.html +14 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/package.json +20 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/001.json +96 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/index.json +89 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.jsx +22 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.module.css +11 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.jsx +53 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.module.css +139 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.jsx +37 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.module.css +72 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.jsx +30 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.module.css +50 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.jsx +159 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.module.css +21 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/main.jsx +12 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.jsx +182 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.module.css +294 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.jsx +147 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.module.css +161 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/global.css +50 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/tokens.css +103 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-react/vite.config.js +6 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/data/tasks.js +67 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/index.html +26 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/router.js +73 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/detail.js +164 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/home.js +53 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/list.js +87 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/app.css +342 -0
- package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/tokens.css +68 -0
- package/.local/skills/h5-to-swiftui/references/css-to-swiftui-map.md +205 -0
- package/.local/skills/h5-to-swiftui/references/design-token-extraction.md +209 -0
- package/.local/skills/h5-to-swiftui/references/high-risk-triage.md +209 -0
- package/.local/skills/h5-to-swiftui/references/render-equivalence-calibration.md +193 -0
- package/.local/skills/h5-to-swiftui/references/stack-detection.md +160 -0
- package/.local/skills/h5-to-swiftui/references/visual-diff-loop-protocol.md +365 -0
- package/.local/skills/h5-to-swiftui/scripts/_calib-consts.mjs +150 -0
- package/.local/skills/h5-to-swiftui/scripts/_imglib.mjs +547 -0
- package/.local/skills/h5-to-swiftui/scripts/_provenance.mjs +123 -0
- package/.local/skills/h5-to-swiftui/scripts/calibrate-render.mjs +625 -0
- package/.local/skills/h5-to-swiftui/scripts/capture-reference.mjs +386 -0
- package/.local/skills/h5-to-swiftui/scripts/detect-stack.mjs +305 -0
- package/.local/skills/h5-to-swiftui/scripts/evaluate-convergence.mjs +1093 -0
- package/.local/skills/h5-to-swiftui/scripts/extract-tokens.mjs +600 -0
- package/.local/skills/h5-to-swiftui/scripts/mark-overlay.mjs +379 -0
- package/.local/skills/h5-to-swiftui/scripts/pixel-diff.mjs +530 -0
- package/.local/skills/h5-to-swiftui/scripts/sim-screenshot.sh +544 -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,318 @@
|
|
|
1
|
+
# macOS Scenes Reference
|
|
2
|
+
|
|
3
|
+
> SwiftUI scene types for macOS apps — `Settings`, `MenuBarExtra`, `WindowGroup`, `Window`, `UtilityWindow`, and `DocumentGroup`. Covers macOS-only scenes and cross-platform scenes with macOS-specific behavior.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Quick Lookup Table](#quick-lookup-table)
|
|
8
|
+
- [Settings (macOS-only)](#settings-macos-only)
|
|
9
|
+
- [MenuBarExtra (macOS-only)](#menubarextra-macos-only)
|
|
10
|
+
- [WindowGroup (macOS behavior)](#windowgroup-macos-behavior)
|
|
11
|
+
- [Window](#window)
|
|
12
|
+
- [UtilityWindow (macOS-only)](#utilitywindow-macos-only)
|
|
13
|
+
- [DocumentGroup](#documentgroup)
|
|
14
|
+
- [Platform Conditionals](#platform-conditionals)
|
|
15
|
+
- [Best Practices](#best-practices)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Lookup Table
|
|
20
|
+
|
|
21
|
+
| API | Availability | macOS-Only? | macOS-Specific Behavior |
|
|
22
|
+
|-----|-------------|:-----------:|------------------------|
|
|
23
|
+
| `WindowGroup` | macOS 11.0+ | No | Multiple window instances, tabbed interface, automatic Window menu commands |
|
|
24
|
+
| `Window` | macOS 13.0+ | No | App quits when sole window closes; adds itself to Windows menu |
|
|
25
|
+
| `UtilityWindow` | macOS 15.0+ | Yes | Floating tool palette; receives `FocusedValues` from active main window |
|
|
26
|
+
| `Settings` | macOS 11.0+ | Yes | Presents preferences window (Cmd+,) |
|
|
27
|
+
| `MenuBarExtra` | macOS 13.0+ | Yes | Persistent icon/menu in the system menu bar |
|
|
28
|
+
| `DocumentGroup` | macOS 11.0+ | No | Document-based menu bar commands (File > New/Open/Save); multiple document windows |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Settings (macOS-only)
|
|
33
|
+
|
|
34
|
+
Presents the app's preferences window, accessible via **Cmd+,** or the app menu. SwiftUI automatically enables the Settings menu item and manages the window lifecycle.
|
|
35
|
+
|
|
36
|
+
```swift
|
|
37
|
+
Settings {
|
|
38
|
+
TabView {
|
|
39
|
+
Tab("General", systemImage: "gear") { GeneralSettingsView() }
|
|
40
|
+
Tab("Advanced", systemImage: "star") { AdvancedSettingsView() }
|
|
41
|
+
}
|
|
42
|
+
.scenePadding()
|
|
43
|
+
.frame(maxWidth: 350, minHeight: 100)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use `TabView` with `Tab` items for multi-pane preferences. Each tab's content is typically a `Form` with `@AppStorage`-backed controls.
|
|
48
|
+
|
|
49
|
+
### SettingsLink (macOS 14.0+)
|
|
50
|
+
|
|
51
|
+
A button that opens the Settings scene. Use for in-app navigation to preferences.
|
|
52
|
+
|
|
53
|
+
```swift
|
|
54
|
+
struct SidebarFooter: View {
|
|
55
|
+
var body: some View {
|
|
56
|
+
SettingsLink {
|
|
57
|
+
Label("Preferences", systemImage: "gear")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### openSettings environment action (macOS 14.0+)
|
|
64
|
+
|
|
65
|
+
Programmatically open (or bring to front) the Settings window.
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
struct OpenSettingsButton: View {
|
|
69
|
+
@Environment(\.openSettings) private var openSettings
|
|
70
|
+
|
|
71
|
+
var body: some View {
|
|
72
|
+
Button("Open Settings") {
|
|
73
|
+
openSettings()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## MenuBarExtra (macOS-only)
|
|
82
|
+
|
|
83
|
+
Renders a persistent control in the system menu bar. Two styles available:
|
|
84
|
+
- **`.menu`** (default) — standard dropdown menu
|
|
85
|
+
- **`.window`** — popover panel with custom SwiftUI views
|
|
86
|
+
|
|
87
|
+
### Menu-style (dropdown)
|
|
88
|
+
|
|
89
|
+
```swift
|
|
90
|
+
MenuBarExtra("My Utility", systemImage: "hammer") {
|
|
91
|
+
Button("Action One") { /* ... */ }
|
|
92
|
+
Button("Action Two") { /* ... */ }
|
|
93
|
+
Divider()
|
|
94
|
+
Button("Quit") { NSApplication.shared.terminate(nil) }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Window-style (popover panel)
|
|
99
|
+
|
|
100
|
+
```swift
|
|
101
|
+
MenuBarExtra("Status", systemImage: "chart.bar") {
|
|
102
|
+
DashboardView()
|
|
103
|
+
.frame(width: 240)
|
|
104
|
+
}
|
|
105
|
+
.menuBarExtraStyle(.window)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Variations:**
|
|
109
|
+
- **Toggleable** — pass `isInserted:` with an `@AppStorage` binding to let users show/hide the extra: `MenuBarExtra("Status", systemImage: "chart.bar", isInserted: $showMenuBarExtra)`
|
|
110
|
+
- **Menu-bar-only app** — use `MenuBarExtra` as the sole scene + set `LSUIElement = true` in Info.plist to hide the Dock icon. The app auto-terminates if the user removes the extra from the menu bar.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## WindowGroup (macOS behavior)
|
|
115
|
+
|
|
116
|
+
On macOS, `WindowGroup` supports:
|
|
117
|
+
- **Multiple window instances** — users can open many windows from File > New Window
|
|
118
|
+
- **Tabbed interface** — users can merge windows into tabs
|
|
119
|
+
- **Automatic Window menu** — commands for window management appear automatically
|
|
120
|
+
|
|
121
|
+
```swift
|
|
122
|
+
@main
|
|
123
|
+
struct Mail: App {
|
|
124
|
+
var body: some Scene {
|
|
125
|
+
// Basic multi-window support
|
|
126
|
+
WindowGroup {
|
|
127
|
+
MailViewer()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Data-presenting window opened programmatically
|
|
131
|
+
WindowGroup("Message", for: Message.ID.self) { $messageID in
|
|
132
|
+
MessageDetail(messageID: messageID)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Open a specific window programmatically
|
|
138
|
+
struct NewMessageButton: View {
|
|
139
|
+
var message: Message
|
|
140
|
+
@Environment(\.openWindow) private var openWindow
|
|
141
|
+
|
|
142
|
+
var body: some View {
|
|
143
|
+
Button("Open Message") {
|
|
144
|
+
openWindow(value: message.id)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **Key difference from `Window`:** `WindowGroup` keeps the app running even after all windows are closed. `Window` (as sole scene) quits the app when closed.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Window
|
|
155
|
+
|
|
156
|
+
A single, unique window scene. The system ensures only one instance exists.
|
|
157
|
+
|
|
158
|
+
```swift
|
|
159
|
+
@main
|
|
160
|
+
struct Mail: App {
|
|
161
|
+
var body: some Scene {
|
|
162
|
+
WindowGroup {
|
|
163
|
+
MailViewer()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Supplementary singleton window
|
|
167
|
+
Window("Connection Doctor", id: "connection-doctor") {
|
|
168
|
+
ConnectionDoctor()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Open programmatically — brings to front if already open
|
|
174
|
+
struct OpenDoctorButton: View {
|
|
175
|
+
@Environment(\.openWindow) private var openWindow
|
|
176
|
+
|
|
177
|
+
var body: some View {
|
|
178
|
+
Button("Connection Doctor") {
|
|
179
|
+
openWindow(id: "connection-doctor")
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Window as sole scene
|
|
186
|
+
|
|
187
|
+
If `Window` is the only scene, the app quits when the window closes:
|
|
188
|
+
|
|
189
|
+
```swift
|
|
190
|
+
@main
|
|
191
|
+
struct VideoCall: App {
|
|
192
|
+
var body: some Scene {
|
|
193
|
+
Window("VideoCall", id: "main") {
|
|
194
|
+
CameraView()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> **Recommendation:** In most cases, prefer `WindowGroup` for the primary scene. Use `Window` for supplementary singleton windows.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## UtilityWindow (macOS-only)
|
|
205
|
+
|
|
206
|
+
A specialized floating window for tool palettes and inspector panels. Available since macOS 15.0.
|
|
207
|
+
|
|
208
|
+
**Key behaviors:**
|
|
209
|
+
- Receives `FocusedValues` from the focused main scene (like menu bar commands)
|
|
210
|
+
- Floats above main windows (default level: `.floating`)
|
|
211
|
+
- Hides when the app is no longer active
|
|
212
|
+
- Only becomes focused when explicitly needed (e.g., clicking the title bar)
|
|
213
|
+
- Dismissible with the Escape key
|
|
214
|
+
- Not minimizable by default
|
|
215
|
+
- Automatically adds a show/hide item to the View menu
|
|
216
|
+
|
|
217
|
+
```swift
|
|
218
|
+
@main
|
|
219
|
+
struct PhotoBrowser: App {
|
|
220
|
+
var body: some Scene {
|
|
221
|
+
WindowGroup {
|
|
222
|
+
PhotoGallery()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
UtilityWindow("Photo Info", id: "photo-info") {
|
|
226
|
+
PhotoInfoViewer()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
struct PhotoInfoViewer: View {
|
|
232
|
+
// Automatically updates based on whichever main window is focused
|
|
233
|
+
@FocusedValue(PhotoSelection.self) private var selectedPhotos
|
|
234
|
+
|
|
235
|
+
var body: some View {
|
|
236
|
+
if let photos = selectedPhotos {
|
|
237
|
+
Text("\(photos.count) photos selected")
|
|
238
|
+
} else {
|
|
239
|
+
Text("No selection")
|
|
240
|
+
.foregroundStyle(.secondary)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
> **Tip:** Remove the automatic View menu item with `.commandsRemoved()` and place a `WindowVisibilityToggle` elsewhere in your commands.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## DocumentGroup
|
|
251
|
+
|
|
252
|
+
Document-based apps with automatic file management. On macOS, provides:
|
|
253
|
+
- **Document-based menu bar commands** (File > New, Open, Save, Revert)
|
|
254
|
+
- **Multiple document windows** simultaneously
|
|
255
|
+
- On iOS, shows a document browser instead
|
|
256
|
+
|
|
257
|
+
```swift
|
|
258
|
+
DocumentGroup(newDocument: TextFile()) { config in
|
|
259
|
+
ContentView(document: config.$document)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The document type must conform to `FileDocument` (value type) or `ReferenceFileDocument` (reference type). Key requirements:
|
|
264
|
+
|
|
265
|
+
```swift
|
|
266
|
+
struct TextFile: FileDocument {
|
|
267
|
+
static var readableContentTypes: [UTType] { [.plainText] }
|
|
268
|
+
var text: String = ""
|
|
269
|
+
init() {}
|
|
270
|
+
init(configuration: ReadConfiguration) throws {
|
|
271
|
+
text = String(data: configuration.file.regularFileContents ?? Data(), encoding: .utf8) ?? ""
|
|
272
|
+
}
|
|
273
|
+
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
|
|
274
|
+
FileWrapper(regularFileWithContents: Data(text.utf8))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
For multiple document types, add additional `DocumentGroup` scenes — use `DocumentGroup(viewing:)` for read-only formats.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Platform Conditionals
|
|
284
|
+
|
|
285
|
+
Always wrap macOS-only scenes in `#if os(macOS)`:
|
|
286
|
+
|
|
287
|
+
```swift
|
|
288
|
+
@main
|
|
289
|
+
struct MyApp: App {
|
|
290
|
+
var body: some Scene {
|
|
291
|
+
WindowGroup {
|
|
292
|
+
ContentView()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#if os(macOS)
|
|
296
|
+
Settings {
|
|
297
|
+
SettingsView()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
MenuBarExtra("Status", systemImage: "bolt") {
|
|
301
|
+
StatusMenu()
|
|
302
|
+
}
|
|
303
|
+
#endif
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Best Practices
|
|
311
|
+
|
|
312
|
+
- **Use `Settings`** for preferences — prefer this over a custom preferences window
|
|
313
|
+
- **Use `MenuBarExtra`** for menu bar items — prefer this over managing AppKit's `NSStatusItem` directly
|
|
314
|
+
- **Use `WindowGroup`** as the primary scene — reserve `Window` for supplementary singletons
|
|
315
|
+
- **Use `UtilityWindow`** for inspectors/palettes — it handles floating, focus, and visibility automatically
|
|
316
|
+
- **Use `DocumentGroup`** for document-based apps — it provides the full File menu and document lifecycle
|
|
317
|
+
- **Gate macOS-only scenes** with `#if os(macOS)` for multiplatform projects
|
|
318
|
+
- **Use `openWindow(id:)`** to open windows programmatically — it brings existing windows to front
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# macOS Views & Components Reference
|
|
2
|
+
|
|
3
|
+
> macOS-specific SwiftUI views, file operations, drag & drop, and AppKit interop. Covers `HSplitView`, `VSplitView`, `Table`, `PasteButton`, file dialogs, cross-app drag & drop, and `NSViewRepresentable`.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Quick Lookup Table](#quick-lookup-table)
|
|
8
|
+
- [HSplitView & VSplitView (macOS-only)](#hsplitview--vsplitview-macos-only)
|
|
9
|
+
- [Table](#table)
|
|
10
|
+
- [PasteButton & CopyButton](#pastebutton--copybutton)
|
|
11
|
+
- [File Operations](#file-operations)
|
|
12
|
+
- [Drag, Drop & Pasteboard](#drag-drop--pasteboard)
|
|
13
|
+
- [AppKit Interop](#appkit-interop)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Lookup Table
|
|
19
|
+
|
|
20
|
+
### Views
|
|
21
|
+
|
|
22
|
+
| API | Availability | macOS-Only? | Usage |
|
|
23
|
+
|-----|-------------|:-----------:|-------|
|
|
24
|
+
| `HSplitView` | macOS 10.15+ | Yes | Horizontal resizable split layout with user-draggable dividers |
|
|
25
|
+
| `VSplitView` | macOS 10.15+ | Yes | Vertical resizable split layout with user-draggable dividers |
|
|
26
|
+
| `Table` | macOS 12.0+ | No | Full multi-column layout with sorting; on iOS compact, columns collapse |
|
|
27
|
+
| `PasteButton` | macOS 10.15+ | No | System button that reads clipboard; does NOT auto-validate on macOS |
|
|
28
|
+
| `CopyButton` | macOS 15.0+ | Yes | System button that copies `Transferable` content to clipboard |
|
|
29
|
+
|
|
30
|
+
### File Operations
|
|
31
|
+
|
|
32
|
+
| API | Availability | macOS-Only? | Usage |
|
|
33
|
+
|-----|-------------|:-----------:|-------|
|
|
34
|
+
| `fileImporter()` | macOS 11.0+ | No | Native NSOpenPanel with column/list/gallery view, sidebar, tags, QuickLook |
|
|
35
|
+
| `fileExporter()` | macOS 11.0+ | No | Native NSSavePanel with format dropdown, tags field |
|
|
36
|
+
| `fileMover()` | macOS 11.0+ | No | Native macOS move panel with Finder-like navigation |
|
|
37
|
+
| `fileDialogMessage(_:)` | macOS 13.0+ | Yes | Custom message text in file dialogs |
|
|
38
|
+
| `fileDialogConfirmationLabel(_:)` | macOS 13.0+ | Yes | Custom confirm button text in file dialogs |
|
|
39
|
+
| `fileExporterFilenameLabel(_:)` | macOS 13.0+ | Yes | Custom filename field label in file exporter |
|
|
40
|
+
|
|
41
|
+
### Drag, Drop & Pasteboard
|
|
42
|
+
|
|
43
|
+
| API | Availability | macOS-Only? | Usage |
|
|
44
|
+
|-----|-------------|:-----------:|-------|
|
|
45
|
+
| `onDrag(_:)` / `draggable(_:)` | macOS 11.0+ | No | Drag image follows cursor; items draggable between apps |
|
|
46
|
+
| `onDrop(of:delegate:)` / `dropDestination(for:action:)` | macOS 11.0+ | No | Accepts drops from any macOS app including Finder |
|
|
47
|
+
|
|
48
|
+
### AppKit Interop
|
|
49
|
+
|
|
50
|
+
| API | Availability | macOS-Only? | Usage |
|
|
51
|
+
|-----|-------------|:-----------:|-------|
|
|
52
|
+
| `NSViewRepresentable` | macOS 10.15+ | Yes | Wrap an AppKit `NSView` in SwiftUI |
|
|
53
|
+
| `NSViewControllerRepresentable` | macOS 10.15+ | Yes | Wrap an AppKit `NSViewController` in SwiftUI |
|
|
54
|
+
| `NSHostingController` | macOS 10.15+ | Yes | Host SwiftUI inside an AppKit view controller |
|
|
55
|
+
| `NSHostingView` | macOS 10.15+ | Yes | Host SwiftUI inside an AppKit `NSView` hierarchy |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## HSplitView & VSplitView (macOS-only)
|
|
60
|
+
|
|
61
|
+
Resizable split layouts with user-draggable dividers. Use for IDE-style panes where all panels are equal peers. `VSplitView` works identically but splits vertically (use `minHeight` instead).
|
|
62
|
+
|
|
63
|
+
```swift
|
|
64
|
+
HSplitView {
|
|
65
|
+
FileTreeView()
|
|
66
|
+
.frame(minWidth: 200)
|
|
67
|
+
CodeEditorView()
|
|
68
|
+
.frame(minWidth: 400)
|
|
69
|
+
PreviewPane()
|
|
70
|
+
.frame(minWidth: 200)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> **When to use which:**
|
|
75
|
+
> - **`NavigationSplitView`** — sidebar-based navigation (sidebar drives content/detail)
|
|
76
|
+
> - **`HSplitView`/`VSplitView`** — IDE-style layouts where all panes are equal peers
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Table
|
|
81
|
+
|
|
82
|
+
For `Table` basics (creation, selection, sorting, adaptive compact layout), see `list-patterns.md`. This section covers macOS-specific table styling.
|
|
83
|
+
|
|
84
|
+
### Table styles
|
|
85
|
+
|
|
86
|
+
```swift
|
|
87
|
+
// Bordered with visible grid lines (macOS-only)
|
|
88
|
+
Table(people) { /* columns */ }
|
|
89
|
+
.tableStyle(.bordered)
|
|
90
|
+
|
|
91
|
+
// Bordered with alternating row backgrounds
|
|
92
|
+
Table(people) { /* columns */ }
|
|
93
|
+
.tableStyle(.bordered(alternatesRowBackgrounds: true))
|
|
94
|
+
|
|
95
|
+
// Inset (no borders)
|
|
96
|
+
Table(people) { /* columns */ }
|
|
97
|
+
.tableStyle(.inset)
|
|
98
|
+
|
|
99
|
+
// Hide column headers
|
|
100
|
+
Table(people) { /* columns */ }
|
|
101
|
+
.tableColumnHeaders(.hidden)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## PasteButton & CopyButton
|
|
107
|
+
|
|
108
|
+
### PasteButton
|
|
109
|
+
|
|
110
|
+
System button that reads clipboard content via `Transferable`. On macOS, it does NOT auto-validate pasteboard changes (unlike iOS).
|
|
111
|
+
|
|
112
|
+
```swift
|
|
113
|
+
struct ClipboardView: View {
|
|
114
|
+
@State private var pastedText = ""
|
|
115
|
+
|
|
116
|
+
var body: some View {
|
|
117
|
+
HStack {
|
|
118
|
+
PasteButton(payloadType: String.self) { strings in
|
|
119
|
+
pastedText = strings[0]
|
|
120
|
+
}
|
|
121
|
+
Divider()
|
|
122
|
+
Text(pastedText)
|
|
123
|
+
Spacer()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### CopyButton (macOS 15.0+, macOS-only)
|
|
130
|
+
|
|
131
|
+
System button that copies `Transferable` content to the clipboard.
|
|
132
|
+
|
|
133
|
+
```swift
|
|
134
|
+
struct CopyableContent: View {
|
|
135
|
+
let shareableText = "Hello, world!"
|
|
136
|
+
|
|
137
|
+
var body: some View {
|
|
138
|
+
HStack {
|
|
139
|
+
Text(shareableText)
|
|
140
|
+
CopyButton(item: shareableText)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## File Operations
|
|
149
|
+
|
|
150
|
+
### fileImporter
|
|
151
|
+
|
|
152
|
+
On macOS, presents a native `NSOpenPanel` with column/list/gallery view, sidebar favorites, tags, and QuickLook.
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
.fileImporter(
|
|
156
|
+
isPresented: $showImporter,
|
|
157
|
+
allowedContentTypes: [.pdf],
|
|
158
|
+
allowsMultipleSelection: false
|
|
159
|
+
) { result in
|
|
160
|
+
if case .success(let urls) = result, let url = urls.first {
|
|
161
|
+
guard url.startAccessingSecurityScopedResource() else { return }
|
|
162
|
+
defer { url.stopAccessingSecurityScopedResource() }
|
|
163
|
+
// use url
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Important:** Always call `startAccessingSecurityScopedResource()` on returned URLs, and `stopAccessingSecurityScopedResource()` when done. These are security-scoped bookmarks — access fails without this.
|
|
169
|
+
|
|
170
|
+
### fileExporter
|
|
171
|
+
|
|
172
|
+
On macOS, presents a native `NSSavePanel` with format dropdown and tags.
|
|
173
|
+
|
|
174
|
+
```swift
|
|
175
|
+
.fileExporter(
|
|
176
|
+
isPresented: $showExporter,
|
|
177
|
+
document: document,
|
|
178
|
+
contentType: .plainText,
|
|
179
|
+
defaultFilename: "MyFile.txt"
|
|
180
|
+
) { result in
|
|
181
|
+
// handle Result<URL, Error>
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### File dialog customization (macOS-only)
|
|
186
|
+
|
|
187
|
+
Customize text in file dialogs with these macOS-specific modifiers:
|
|
188
|
+
|
|
189
|
+
```swift
|
|
190
|
+
// Custom message and confirm button on file importer
|
|
191
|
+
.fileImporter(
|
|
192
|
+
isPresented: $showImporter,
|
|
193
|
+
allowedContentTypes: [.image]
|
|
194
|
+
) { result in
|
|
195
|
+
// handle result
|
|
196
|
+
}
|
|
197
|
+
.fileDialogMessage("Select an image to use as your profile photo")
|
|
198
|
+
.fileDialogConfirmationLabel("Use This Photo")
|
|
199
|
+
|
|
200
|
+
// Custom filename label on file exporter
|
|
201
|
+
.fileExporter(
|
|
202
|
+
isPresented: $showExporter,
|
|
203
|
+
document: myDocument,
|
|
204
|
+
contentType: .png
|
|
205
|
+
) { result in
|
|
206
|
+
// handle result
|
|
207
|
+
}
|
|
208
|
+
.fileExporterFilenameLabel("Export As:")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Drag, Drop & Pasteboard
|
|
214
|
+
|
|
215
|
+
On macOS, drag and drop works **across applications** (e.g., drag from your app to Finder, Mail, or other apps).
|
|
216
|
+
|
|
217
|
+
### Modern approach (Transferable)
|
|
218
|
+
|
|
219
|
+
```swift
|
|
220
|
+
// Drag source
|
|
221
|
+
struct DraggableCard: View {
|
|
222
|
+
let item: MyItem
|
|
223
|
+
|
|
224
|
+
var body: some View {
|
|
225
|
+
Text(item.title)
|
|
226
|
+
.draggable(item) // Requires Transferable conformance
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Drop target
|
|
231
|
+
struct DropZone: View {
|
|
232
|
+
@State private var droppedItems: [MyItem] = []
|
|
233
|
+
|
|
234
|
+
var body: some View {
|
|
235
|
+
VStack {
|
|
236
|
+
ForEach(droppedItems) { item in
|
|
237
|
+
Text(item.title)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
.dropDestination(for: MyItem.self) { items, location in
|
|
241
|
+
droppedItems.append(contentsOf: items)
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
244
|
+
.frame(width: 300, height: 200)
|
|
245
|
+
.border(.secondary)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Legacy approach (NSItemProvider)
|
|
251
|
+
|
|
252
|
+
```swift
|
|
253
|
+
// Drag source
|
|
254
|
+
Image(systemName: "doc")
|
|
255
|
+
.onDrag {
|
|
256
|
+
NSItemProvider(object: fileURL as NSURL)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Drop target
|
|
260
|
+
Text("Drop files here")
|
|
261
|
+
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
|
|
262
|
+
// handle providers
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## AppKit Interop
|
|
270
|
+
|
|
271
|
+
### NSViewRepresentable (macOS-only)
|
|
272
|
+
|
|
273
|
+
Wraps an AppKit `NSView` for use in SwiftUI. Implement `makeNSView(context:)` and `updateNSView(_:context:)`.
|
|
274
|
+
|
|
275
|
+
```swift
|
|
276
|
+
struct WebView: NSViewRepresentable {
|
|
277
|
+
let url: URL
|
|
278
|
+
func makeNSView(context: Context) -> WKWebView { WKWebView() }
|
|
279
|
+
func updateNSView(_ nsView: WKWebView, context: Context) {
|
|
280
|
+
nsView.load(URLRequest(url: url))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### NSViewRepresentable with Coordinator
|
|
286
|
+
|
|
287
|
+
Use a Coordinator to forward delegate/target-action callbacks to SwiftUI.
|
|
288
|
+
|
|
289
|
+
```swift
|
|
290
|
+
struct SearchField: NSViewRepresentable {
|
|
291
|
+
@Binding var text: String
|
|
292
|
+
|
|
293
|
+
func makeNSView(context: Context) -> NSSearchField {
|
|
294
|
+
let field = NSSearchField()
|
|
295
|
+
field.delegate = context.coordinator
|
|
296
|
+
return field
|
|
297
|
+
}
|
|
298
|
+
func updateNSView(_ nsView: NSSearchField, context: Context) {
|
|
299
|
+
nsView.stringValue = text
|
|
300
|
+
}
|
|
301
|
+
func makeCoordinator() -> Coordinator { Coordinator(text: $text) }
|
|
302
|
+
|
|
303
|
+
class Coordinator: NSObject, NSSearchFieldDelegate {
|
|
304
|
+
var text: Binding<String>
|
|
305
|
+
init(text: Binding<String>) { self.text = text }
|
|
306
|
+
func controlTextDidChange(_ obj: Notification) {
|
|
307
|
+
if let field = obj.object as? NSSearchField {
|
|
308
|
+
text.wrappedValue = field.stringValue
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> **Warning:** Never set `frame`/`bounds` directly on the managed `NSView` — SwiftUI owns the layout.
|
|
316
|
+
|
|
317
|
+
### NSViewControllerRepresentable (macOS-only)
|
|
318
|
+
|
|
319
|
+
Wraps an AppKit `NSViewController` for use in SwiftUI.
|
|
320
|
+
|
|
321
|
+
```swift
|
|
322
|
+
struct MapViewWrapper: NSViewControllerRepresentable {
|
|
323
|
+
func makeNSViewController(context: Context) -> MapViewController {
|
|
324
|
+
MapViewController()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
func updateNSViewController(_ nsViewController: MapViewController, context: Context) {
|
|
328
|
+
// Update the controller when SwiftUI state changes
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### NSHostingController & NSHostingView (macOS-only)
|
|
334
|
+
|
|
335
|
+
Host SwiftUI content inside AppKit (reverse direction — AppKit app embedding SwiftUI views).
|
|
336
|
+
|
|
337
|
+
```swift
|
|
338
|
+
// Host SwiftUI as a view controller
|
|
339
|
+
let hostingController = NSHostingController(rootView: MySwiftUIView())
|
|
340
|
+
window.contentViewController = hostingController
|
|
341
|
+
|
|
342
|
+
// Host SwiftUI directly as an NSView
|
|
343
|
+
let hostingView = NSHostingView(rootView: MySwiftUIView())
|
|
344
|
+
someNSView.addSubview(hostingView)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Best Practices
|
|
350
|
+
|
|
351
|
+
- **Use `NavigationSplitView`** for sidebar-driven navigation — reserve `HSplitView`/`VSplitView` for IDE-style equal peer panes
|
|
352
|
+
- **Make `Table` adaptive** — handle compact size classes by showing combined info in the first column
|
|
353
|
+
- **Always call `startAccessingSecurityScopedResource()`** on URLs from `fileImporter` — they are security-scoped
|
|
354
|
+
- **Use `Transferable`** for drag & drop (modern) — fall back to `NSItemProvider` only for legacy compatibility
|
|
355
|
+
- **Use `NSViewRepresentable` with Coordinator** when you need delegate callbacks from AppKit views
|
|
356
|
+
- **Never set `frame`/`bounds`** directly on views managed by `NSViewRepresentable` — SwiftUI owns the layout
|
|
357
|
+
- **Prefer native SwiftUI** over AppKit interop when possible — only use `NSViewRepresentable` for features SwiftUI doesn't provide
|