☰
swiftui-expert-skill - macos-views
2026/10/12 3:42:27 网站建设 项目流程

macOS 视图与组件参考

macOS 特定的 SwiftUI 视图、文件操作、拖放以及 AppKit 互操作。涵盖HSplitView、VSplitView、Table、PasteButton、文件对话框、跨应用拖放和NSViewRepresentable。

目录

  • 快速查找表
  • HSplitView 与 VSplitView(macOS 专属)
  • Table
  • PasteButton 与 CopyButton
  • 文件操作
  • 拖放与剪贴板
  • AppKit 互操作
  • 最佳实践

快速查找表

视图

API可用性仅 macOS?用途
HSplitViewmacOS 10.15+是带用户可拖动分隔线的水平可调整分割布局
VSplitViewmacOS 10.15+是带用户可拖动分隔线的垂直可调整分割布局
TablemacOS 12.0+否带排序的完整多列布局;在 iOS 紧凑模式下,列会折叠
PasteButtonmacOS 10.15+否读取剪贴板的系统按钮;在 macOS 上不会自动验证
CopyButtonmacOS 15.0+是将Transferable内容复制到剪贴板的系统按钮

文件操作

API可用性仅 macOS?用途
fileImporter()macOS 11.0+否带列/列表/画廊视图、侧边栏、标签和 QuickLook 的原生 NSOpenPanel
fileExporter()macOS 11.0+否带格式下拉框和标签字段的原生 NSSavePanel
fileMover()macOS 11.0+否带 Finder 式导航的原生 macOS 移动面板
fileDialogMessage(_:)macOS 13.0+是文件对话框中的自定义消息文本
fileDialogConfirmationLabel(_:)macOS 13.0+是文件对话框中自定义的确认按钮文本
fileExporterFilenameLabel(_:)macOS 13.0+是文件导出器中自定义的文件名字段标签

拖放与剪贴板

API可用性仅 macOS?用途
onDrag(_:)/draggable(_:)macOS 11.0+否拖动图像跟随光标;项目可在应用之间拖动
onDrop(of:delegate:)/dropDestination(for:action:)macOS 11.0+否接受来自任何 macOS 应用(包括 Finder)的拖放

AppKit 互操作

API可用性仅 macOS?用途
NSViewRepresentablemacOS 10.15+是将 AppKitNSView包裹在 SwiftUI 中
NSViewControllerRepresentablemacOS 10.15+是将 AppKitNSViewController包裹在 SwiftUI 中
NSHostingControllermacOS 10.15+是在 AppKit 视图控制器中承载 SwiftUI
NSHostingViewmacOS 10.15+是在 AppKitNSView层级中承载 SwiftUI

HSplitView 与 VSplitView(macOS 专属)

带用户可拖动分隔线的可调整分割布局。用于所有面板都是同级对等的 IDE 风格窗格。VSplitView工作方式相同,但垂直分割(改用minHeight)。

HSplitView{FileTreeView().frame(minWidth:200)CodeEditorView().frame(minWidth:400)PreviewPane().frame(minWidth:200)}

何时使用哪种:

  • NavigationSplitView—— 基于侧边栏的导航(侧边栏驱动内容/详情)
  • HSplitView/VSplitView—— 所有窗格都是同级对等的 IDE 风格布局

Table

关于Table的基础(创建、选择、排序、自适应紧凑布局),请参阅list-patterns.md。本节涵盖 macOS 特定的表格样式。

Table 样式

// 带可见网格线的边框样式(macOS 专属)Table(people){/* columns */}.tableStyle(.bordered)// 带交替行背景的边框样式Table(people){/* columns */}.tableStyle(.bordered(alternatesRowBackgrounds:true))// Inset(无边框)Table(people){/* columns */}.tableStyle(.inset)// 隐藏列标题Table(people){/* columns */}.tableColumnHeaders(.hidden)

PasteButton 与 CopyButton

PasteButton

通过Transferable读取剪贴板内容的系统按钮。在 macOS 上,它不会自动验证剪贴板变化(与 iOS 不同)。

structClipboardView:View{@StateprivatevarpastedText=""varbody:someView{HStack{PasteButton(payloadType:String.self){stringsinpastedText=strings[0]}Divider()Text(pastedText)Spacer()}}}

CopyButton(macOS 15.0+、macOS 专属)

将Transferable内容复制到剪贴板的系统按钮。

structCopyableContent:View{letshareableText="Hello, world!"varbody:someView{HStack{Text(shareableText)CopyButton(item:shareableText)}}}

文件操作

fileImporter

在 macOS 上,呈现带列/列表/画廊视图、侧边栏收藏、标签和 QuickLook 的原生NSOpenPanel。

.fileImporter(isPresented:$showImporter,allowedContentTypes:[.pdf],allowsMultipleSelection:false){resultinifcase.success(leturls)=result,leturl=urls.first{guardurl.startAccessingSecurityScopedResource()else{return}defer{url.stopAccessingSecurityScopedResource()}// use url}}

重要:始终对返回的 URL 调用startAccessingSecurityScopedResource(),并在完成后调用stopAccessingSecurityScopedResource()。这些是安全作用域书签——没有它,访问会失败。

fileExporter

在 macOS 上,呈现带格式下拉框和标签的原生NSSavePanel。

.fileExporter(isPresented:$showExporter,document:document,contentType:.plainText,defaultFilename:"MyFile.txt"){resultin// handle Result<URL, Error>}

文件对话框自定义(macOS 专属)

使用这些 macOS 特定修饰符自定义文件对话框中的文本:

// 文件导入器上的自定义消息和确认按钮.fileImporter(isPresented:$showImporter,allowedContentTypes:[.image]){resultin// handle result}.fileDialogMessage("Select an image to use as your profile photo").fileDialogConfirmationLabel("Use This Photo")// 文件导出器上的自定义文件名标签.fileExporter(isPresented:$showExporter,document:myDocument,contentType:.png){resultin// handle result}.fileExporterFilenameLabel("Export As:")

拖放与剪贴板

在 macOS 上,拖放跨应用工作(例如,从你的应用拖到 Finder、Mail 或其他应用)。

现代方法(Transferable)

// 拖动源structDraggableCard:View{letitem:MyItemvarbody:someView{Text(item.title).draggable(item)// 需要 Transferable 遵循}}// 拖放目标structDropZone:View{@StateprivatevardroppedItems:[MyItem]=[]varbody:someView{VStack{ForEach(droppedItems){iteminText(item.title)}}.dropDestination(for:MyItem.self){items,locationindroppedItems.append(contentsOf:items)returntrue}.frame(width:300,height:200).border(.secondary)}}

传统方法(NSItemProvider)

// 拖动源Image(systemName:"doc").onDrag{NSItemProvider(object:fileURLasNSURL)}// 拖放目标Text("Drop files here").onDrop(of:[.fileURL],isTargeted:nil){providersin// handle providersreturntrue}

AppKit 互操作

NSViewRepresentable(macOS 专属)

将 AppKitNSView包裹起来以在 SwiftUI 中使用。实现makeNSView(context:)和updateNSView(_:context:)。

structWebView:NSViewRepresentable{leturl:URLfuncmakeNSView(context:Context)->WKWebView{WKWebView()}funcupdateNSView(_nsView:WKWebView,context:Context){nsView.load(URLRequest(url:url))}}

带 Coordinator 的 NSViewRepresentable

使用 Coordinator 将 delegate/target-action 回调转发给 SwiftUI。

structSearchField:NSViewRepresentable{@Bindingvartext:StringfuncmakeNSView(context:Context)->NSSearchField{letfield=NSSearchField()field.delegate=context.coordinatorreturnfield}funcupdateNSView(_nsView:NSSearchField,context:Context){nsView.stringValue=text}funcmakeCoordinator()->Coordinator{Coordinator(text:$text)}classCoordinator:NSObject,NSSearchFieldDelegate{vartext:Binding<String>init(text:Binding<String>){self.text=text}funccontrolTextDidChange(_obj:Notification){ifletfield=obj.objectas?NSSearchField{text.wrappedValue=field.stringValue}}}}

警告:绝不要直接在被管理的NSView上设置frame/bounds——SwiftUI 拥有布局。

NSViewControllerRepresentable(macOS 专属)

将 AppKitNSViewController包裹起来以在 SwiftUI 中使用。

structMapViewWrapper:NSViewControllerRepresentable{funcmakeNSViewController(context:Context)->MapViewController{MapViewController()}funcupdateNSViewController(_nsViewController:MapViewController,context:Context){// Update the controller when SwiftUI state changes}}

NSHostingController 与 NSHostingView(macOS 专属)

在 AppKit 内部承载 SwiftUI 内容(反向——AppKit 应用嵌入 SwiftUI 视图)。

// 将 SwiftUI 作为视图控制器承载lethostingController=NSHostingController(rootView:MySwiftUIView())window.contentViewController=hostingController// 将 SwiftUI 直接作为 NSView 承载lethostingView=NSHostingView(rootView:MySwiftUIView())someNSView.addSubview(hostingView)

最佳实践

  • 侧边栏驱动的导航使用NavigationSplitView—— 将HSplitView/VSplitView保留给 IDE 风格的对等窗格
  • 让Table具备自适应性—— 通过在第一列显示组合信息来处理紧凑尺寸类别
  • 对fileImporter返回的 URL 始终调用startAccessingSecurityScopedResource()—— 它们是安全作用域的
  • 拖放使用Transferable(现代)—— 仅出于遗留兼容性才回退到NSItemProvider
  • 当你需要 AppKit 视图的 delegate 回调时,使用带 Coordinator 的NSViewRepresentable
  • 绝不在NSViewRepresentable管理的视图上直接设置frame/bounds—— SwiftUI 拥有布局
  • 尽可能优先使用原生 SwiftUI而不是 AppKit 互操作 —— 仅对 SwiftUI 不提供的功能使用NSViewRepresentable

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询