Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reviews, improves, and writes SwiftUI code following state management, view composition, performance, and iOS 26+ Liquid Glass best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/macos-views.md
1# macOS Views & Components Reference23> macOS-specific SwiftUI views, file operations, drag & drop, and AppKit interop. Covers `HSplitView`, `VSplitView`, `Table`, `PasteButton`, file dialogs, cross-app drag & drop, and `NSViewRepresentable`.45## Table of Contents67- [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)1516---1718## Quick Lookup Table1920### Views2122| 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 |2930### File Operations3132| 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 |4041### Drag, Drop & Pasteboard4243| 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 |4748### AppKit Interop4950| 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 |5657---5859## HSplitView & VSplitView (macOS-only)6061Resizable 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).6263```swift64HSplitView {65FileTreeView()66.frame(minWidth: 200)67CodeEditorView()68.frame(minWidth: 400)69PreviewPane()70.frame(minWidth: 200)71}72```7374> **When to use which:**75> - **`NavigationSplitView`** — sidebar-based navigation (sidebar drives content/detail)76> - **`HSplitView`/`VSplitView`** — IDE-style layouts where all panes are equal peers7778---7980## Table8182For `Table` basics (creation, selection, sorting, adaptive compact layout), see `list-patterns.md`. This section covers macOS-specific table styling.8384### Table styles8586```swift87// Bordered with visible grid lines (macOS-only)88Table(people) { /* columns */ }89.tableStyle(.bordered)9091// Bordered with alternating row backgrounds92Table(people) { /* columns */ }93.tableStyle(.bordered(alternatesRowBackgrounds: true))9495// Inset (no borders)96Table(people) { /* columns */ }97.tableStyle(.inset)9899// Hide column headers100Table(people) { /* columns */ }101.tableColumnHeaders(.hidden)102```103104---105106## PasteButton & CopyButton107108### PasteButton109110System button that reads clipboard content via `Transferable`. On macOS, it does NOT auto-validate pasteboard changes (unlike iOS).111112```swift113struct ClipboardView: View {114@State private var pastedText = ""115116var body: some View {117HStack {118PasteButton(payloadType: String.self) { strings in119pastedText = strings[0]120}121Divider()122Text(pastedText)123Spacer()124}125}126}127```128129### CopyButton (macOS 15.0+, macOS-only)130131System button that copies `Transferable` content to the clipboard.132133```swift134struct CopyableContent: View {135let shareableText = "Hello, world!"136137var body: some View {138HStack {139Text(shareableText)140CopyButton(item: shareableText)141}142}143}144```145146---147148## File Operations149150### fileImporter151152On macOS, presents a native `NSOpenPanel` with column/list/gallery view, sidebar favorites, tags, and QuickLook.153154```swift155.fileImporter(156isPresented: $showImporter,157allowedContentTypes: [.pdf],158allowsMultipleSelection: false159) { result in160if case .success(let urls) = result, let url = urls.first {161guard url.startAccessingSecurityScopedResource() else { return }162defer { url.stopAccessingSecurityScopedResource() }163// use url164}165}166```167168> **Important:** Always call `startAccessingSecurityScopedResource()` on returned URLs, and `stopAccessingSecurityScopedResource()` when done. These are security-scoped bookmarks — access fails without this.169170### fileExporter171172On macOS, presents a native `NSSavePanel` with format dropdown and tags.173174```swift175.fileExporter(176isPresented: $showExporter,177document: document,178contentType: .plainText,179defaultFilename: "MyFile.txt"180) { result in181// handle Result<URL, Error>182}183```184185### File dialog customization (macOS-only)186187Customize text in file dialogs with these macOS-specific modifiers:188189```swift190// Custom message and confirm button on file importer191.fileImporter(192isPresented: $showImporter,193allowedContentTypes: [.image]194) { result in195// handle result196}197.fileDialogMessage("Select an image to use as your profile photo")198.fileDialogConfirmationLabel("Use This Photo")199200// Custom filename label on file exporter201.fileExporter(202isPresented: $showExporter,203document: myDocument,204contentType: .png205) { result in206// handle result207}208.fileExporterFilenameLabel("Export As:")209```210211---212213## Drag, Drop & Pasteboard214215On macOS, drag and drop works **across applications** (e.g., drag from your app to Finder, Mail, or other apps).216217### Modern approach (Transferable)218219```swift220// Drag source221struct DraggableCard: View {222let item: MyItem223224var body: some View {225Text(item.title)226.draggable(item) // Requires Transferable conformance227}228}229230// Drop target231struct DropZone: View {232@State private var droppedItems: [MyItem] = []233234var body: some View {235VStack {236ForEach(droppedItems) { item in237Text(item.title)238}239}240.dropDestination(for: MyItem.self) { items, location in241droppedItems.append(contentsOf: items)242return true243}244.frame(width: 300, height: 200)245.border(.secondary)246}247}248```249250### Legacy approach (NSItemProvider)251252```swift253// Drag source254Image(systemName: "doc")255.onDrag {256NSItemProvider(object: fileURL as NSURL)257}258259// Drop target260Text("Drop files here")261.onDrop(of: [.fileURL], isTargeted: nil) { providers in262// handle providers263return true264}265```266267---268269## AppKit Interop270271### NSViewRepresentable (macOS-only)272273Wraps an AppKit `NSView` for use in SwiftUI. Implement `makeNSView(context:)` and `updateNSView(_:context:)`.274275```swift276struct WebView: NSViewRepresentable {277let url: URL278func makeNSView(context: Context) -> WKWebView { WKWebView() }279func updateNSView(_ nsView: WKWebView, context: Context) {280nsView.load(URLRequest(url: url))281}282}283```284285### NSViewRepresentable with Coordinator286287Use a Coordinator to forward delegate/target-action callbacks to SwiftUI.288289```swift290struct SearchField: NSViewRepresentable {291@Binding var text: String292293func makeNSView(context: Context) -> NSSearchField {294let field = NSSearchField()295field.delegate = context.coordinator296return field297}298func updateNSView(_ nsView: NSSearchField, context: Context) {299nsView.stringValue = text300}301func makeCoordinator() -> Coordinator { Coordinator(text: $text) }302303class Coordinator: NSObject, NSSearchFieldDelegate {304var text: Binding<String>305init(text: Binding<String>) { self.text = text }306func controlTextDidChange(_ obj: Notification) {307if let field = obj.object as? NSSearchField {308text.wrappedValue = field.stringValue309}310}311}312}313```314315> **Warning:** Never set `frame`/`bounds` directly on the managed `NSView` — SwiftUI owns the layout.316317### NSViewControllerRepresentable (macOS-only)318319Wraps an AppKit `NSViewController` for use in SwiftUI.320321```swift322struct MapViewWrapper: NSViewControllerRepresentable {323func makeNSViewController(context: Context) -> MapViewController {324MapViewController()325}326327func updateNSViewController(_ nsViewController: MapViewController, context: Context) {328// Update the controller when SwiftUI state changes329}330}331```332333### NSHostingController & NSHostingView (macOS-only)334335Host SwiftUI content inside AppKit (reverse direction — AppKit app embedding SwiftUI views).336337```swift338// Host SwiftUI as a view controller339let hostingController = NSHostingController(rootView: MySwiftUIView())340window.contentViewController = hostingController341342// Host SwiftUI directly as an NSView343let hostingView = NSHostingView(rootView: MySwiftUIView())344someNSView.addSubview(hostingView)345```346347---348349## Best Practices350351- **Use `NavigationSplitView`** for sidebar-driven navigation — reserve `HSplitView`/`VSplitView` for IDE-style equal peer panes352- **Make `Table` adaptive** — handle compact size classes by showing combined info in the first column353- **Always call `startAccessingSecurityScopedResource()`** on URLs from `fileImporter` — they are security-scoped354- **Use `Transferable`** for drag & drop (modern) — fall back to `NSItemProvider` only for legacy compatibility355- **Use `NSViewRepresentable` with Coordinator** when you need delegate callbacks from AppKit views356- **Never set `frame`/`bounds`** directly on views managed by `NSViewRepresentable` — SwiftUI owns the layout357- **Prefer native SwiftUI** over AppKit interop when possible — only use `NSViewRepresentable` for features SwiftUI doesn't provide358