.agents/skills/swiftui-expert-skill/references/macos-views.md
macOS-specific SwiftUI views, file operations, drag & drop, and AppKit interop. Covers
HSplitView,VSplitView,Table,PasteButton, file dialogs, cross-app drag & drop, andNSViewRepresentable.
| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
HSplitView | macOS 10.15+ | Yes | Horizontal resizable split layout with user-draggable dividers |
VSplitView | macOS 10.15+ | Yes | Vertical resizable split layout with user-draggable dividers |
Table | macOS 12.0+ | No | Full multi-column layout with sorting; on iOS compact, columns collapse |
PasteButton | macOS 10.15+ | No | System button that reads clipboard; does NOT auto-validate on macOS |
CopyButton | macOS 15.0+ | Yes | System button that copies Transferable content to clipboard |
| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
fileImporter() | macOS 11.0+ | No | Native NSOpenPanel with column/list/gallery view, sidebar, tags, QuickLook |
fileExporter() | macOS 11.0+ | No | Native NSSavePanel with format dropdown, tags field |
fileMover() | macOS 11.0+ | No | Native macOS move panel with Finder-like navigation |
fileDialogMessage(_:) | macOS 13.0+ | Yes | Custom message text in file dialogs |
fileDialogConfirmationLabel(_:) | macOS 13.0+ | Yes | Custom confirm button text in file dialogs |
fileExporterFilenameLabel(_:) | macOS 13.0+ | Yes | Custom filename field label in file exporter |
| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
onDrag(_:) / draggable(_:) | macOS 11.0+ | No | Drag image follows cursor; items draggable between apps |
onDrop(of:delegate:) / dropDestination(for:action:) | macOS 11.0+ | No | Accepts drops from any macOS app including Finder |
| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
NSViewRepresentable | macOS 10.15+ | Yes | Wrap an AppKit NSView in SwiftUI |
NSViewControllerRepresentable | macOS 10.15+ | Yes | Wrap an AppKit NSViewController in SwiftUI |
NSHostingController | macOS 10.15+ | Yes | Host SwiftUI inside an AppKit view controller |
NSHostingView | macOS 10.15+ | Yes | Host SwiftUI inside an AppKit NSView hierarchy |
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).
HSplitView {
FileTreeView()
.frame(minWidth: 200)
CodeEditorView()
.frame(minWidth: 400)
PreviewPane()
.frame(minWidth: 200)
}
When to use which:
NavigationSplitView— sidebar-based navigation (sidebar drives content/detail)HSplitView/VSplitView— IDE-style layouts where all panes are equal peers
For Table basics (creation, selection, sorting, adaptive compact layout), see list-patterns.md. This section covers macOS-specific table styling.
// Bordered with visible grid lines (macOS-only)
Table(people) { /* columns */ }
.tableStyle(.bordered)
// Bordered with alternating row backgrounds
Table(people) { /* columns */ }
.tableStyle(.bordered(alternatesRowBackgrounds: true))
// Inset (no borders)
Table(people) { /* columns */ }
.tableStyle(.inset)
// Hide column headers
Table(people) { /* columns */ }
.tableColumnHeaders(.hidden)
System button that reads clipboard content via Transferable. On macOS, it does NOT auto-validate pasteboard changes (unlike iOS).
struct ClipboardView: View {
@State private var pastedText = ""
var body: some View {
HStack {
PasteButton(payloadType: String.self) { strings in
pastedText = strings[0]
}
Divider()
Text(pastedText)
Spacer()
}
}
}
System button that copies Transferable content to the clipboard.
struct CopyableContent: View {
let shareableText = "Hello, world!"
var body: some View {
HStack {
Text(shareableText)
CopyButton(item: shareableText)
}
}
}
On macOS, presents a native NSOpenPanel with column/list/gallery view, sidebar favorites, tags, and QuickLook.
.fileImporter(
isPresented: $showImporter,
allowedContentTypes: [.pdf],
allowsMultipleSelection: false
) { result in
if case .success(let urls) = result, let url = urls.first {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
// use url
}
}
Important: Always call
startAccessingSecurityScopedResource()on returned URLs, andstopAccessingSecurityScopedResource()when done. These are security-scoped bookmarks — access fails without this.
On macOS, presents a native NSSavePanel with format dropdown and tags.
.fileExporter(
isPresented: $showExporter,
document: document,
contentType: .plainText,
defaultFilename: "MyFile.txt"
) { result in
// handle Result<URL, Error>
}
Customize text in file dialogs with these macOS-specific modifiers:
// Custom message and confirm button on file importer
.fileImporter(
isPresented: $showImporter,
allowedContentTypes: [.image]
) { result in
// handle result
}
.fileDialogMessage("Select an image to use as your profile photo")
.fileDialogConfirmationLabel("Use This Photo")
// Custom filename label on file exporter
.fileExporter(
isPresented: $showExporter,
document: myDocument,
contentType: .png
) { result in
// handle result
}
.fileExporterFilenameLabel("Export As:")
On macOS, drag and drop works across applications (e.g., drag from your app to Finder, Mail, or other apps).
// Drag source
struct DraggableCard: View {
let item: MyItem
var body: some View {
Text(item.title)
.draggable(item) // Requires Transferable conformance
}
}
// Drop target
struct DropZone: View {
@State private var droppedItems: [MyItem] = []
var body: some View {
VStack {
ForEach(droppedItems) { item in
Text(item.title)
}
}
.dropDestination(for: MyItem.self) { items, location in
droppedItems.append(contentsOf: items)
return true
}
.frame(width: 300, height: 200)
.border(.secondary)
}
}
// Drag source
Image(systemName: "doc")
.onDrag {
NSItemProvider(object: fileURL as NSURL)
}
// Drop target
Text("Drop files here")
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
// handle providers
return true
}
Wraps an AppKit NSView for use in SwiftUI. Implement makeNSView(context:) and updateNSView(_:context:).
struct WebView: NSViewRepresentable {
let url: URL
func makeNSView(context: Context) -> WKWebView { WKWebView() }
func updateNSView(_ nsView: WKWebView, context: Context) {
nsView.load(URLRequest(url: url))
}
}
Use a Coordinator to forward delegate/target-action callbacks to SwiftUI.
struct SearchField: NSViewRepresentable {
@Binding var text: String
func makeNSView(context: Context) -> NSSearchField {
let field = NSSearchField()
field.delegate = context.coordinator
return field
}
func updateNSView(_ nsView: NSSearchField, context: Context) {
nsView.stringValue = text
}
func makeCoordinator() -> Coordinator { Coordinator(text: $text) }
class Coordinator: NSObject, NSSearchFieldDelegate {
var text: Binding<String>
init(text: Binding<String>) { self.text = text }
func controlTextDidChange(_ obj: Notification) {
if let field = obj.object as? NSSearchField {
text.wrappedValue = field.stringValue
}
}
}
}
Warning: Never set
frame/boundsdirectly on the managedNSView— SwiftUI owns the layout.
Wraps an AppKit NSViewController for use in SwiftUI.
struct MapViewWrapper: NSViewControllerRepresentable {
func makeNSViewController(context: Context) -> MapViewController {
MapViewController()
}
func updateNSViewController(_ nsViewController: MapViewController, context: Context) {
// Update the controller when SwiftUI state changes
}
}
Host SwiftUI content inside AppKit (reverse direction — AppKit app embedding SwiftUI views).
// Host SwiftUI as a view controller
let hostingController = NSHostingController(rootView: MySwiftUIView())
window.contentViewController = hostingController
// Host SwiftUI directly as an NSView
let hostingView = NSHostingView(rootView: MySwiftUIView())
someNSView.addSubview(hostingView)
NavigationSplitView for sidebar-driven navigation — reserve HSplitView/VSplitView for IDE-style equal peer panesTable adaptive — handle compact size classes by showing combined info in the first columnstartAccessingSecurityScopedResource() on URLs from fileImporter — they are security-scopedTransferable for drag & drop (modern) — fall back to NSItemProvider only for legacy compatibilityNSViewRepresentable with Coordinator when you need delegate callbacks from AppKit viewsframe/bounds directly on views managed by NSViewRepresentable — SwiftUI owns the layoutNSViewRepresentable for features SwiftUI doesn't provide