如何为SwiftUI中NSPopover的尺寸变化添加动画效果?
实现带尺寸动画的NSPopover(SwiftUI + NSViewRepresentable)
步骤1:封装NSPopover的SwiftUI桥接视图
通过NSViewRepresentable实现对NSPopover的控制,同时开启尺寸动画支持:
import SwiftUI struct NSPopoverWrapper<Content: View>: NSViewRepresentable { @Binding var isShown: Bool let contentView: Content let preferredEdge: NSRectEdge = .maxY func makeNSView(context: Context) -> NSView { let placeholderView = NSView() placeholderView.frame = .zero return placeholderView } func updateNSView(_ nsView: NSView, context: Context) { let coordinator = context.coordinator if isShown, coordinator.popover == nil { let popover = NSPopover() popover.behavior = .transient popover.animates = true // 开启尺寸变化动画 popover.contentViewController = NSHostingController(rootView: contentView) // 初始尺寸匹配内容视图 if let hostingVC = popover.contentViewController as? NSHostingController<Content> { coordinator.contentSize = hostingVC.view.intrinsicContentSize popover.contentSize = coordinator.contentSize } coordinator.popover = popover popover.show(relativeTo: nsView.bounds, of: nsView, preferredEdge: preferredEdge) } else if !isShown, let popover = coordinator.popover { popover.performClose(nil) coordinator.popover = nil } // 实时同步内容视图尺寸变化 if let hostingVC = coordinator.popover?.contentViewController as? NSHostingController<Content>, let newSize = hostingVC.view.intrinsicContentSize as? NSSize, newSize != coordinator.contentSize { coordinator.contentSize = newSize coordinator.popover?.contentSize = newSize } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, NSPopoverDelegate { var parent: NSPopoverWrapper var popover: NSPopover? var contentSize: NSSize = .zero init(_ parent: NSPopoverWrapper) { self.parent = parent super.init() } // 关闭popover时同步绑定状态 func popoverDidClose(_ notification: Notification) { parent.isShown = false } } }
步骤2:创建动态内容视图
模拟不同对象类型对应的字段集合,尺寸会随类型变化:
enum ObjectType { case basic, advanced } struct DynamicFormView: View { let objectType: ObjectType var body: some View { VStack(alignment: .leading, spacing: 16) { Text(objectType == .basic ? "创建基础对象" : "创建高级对象") .font(.headline) Group { if objectType == .basic { TextField("名称", text: .constant("")) TextField("描述", text: .constant("")) } else { TextField("名称", text: .constant("")) TextField("描述", text: .constant("")) TextField("编号", text: .constant("")) TextField("有效期", text: .constant("")) TextField("状态", text: .constant("")) } } .textFieldStyle(.roundedBorder) .frame(width: 300) Button("确认创建") {} .frame(maxWidth: .infinity, alignment: .trailing) } .padding() .background(Color.windowBackgroundColor) } }
步骤3:在主视图中集成使用
将popover与按钮绑定,切换对象类型时即可看到平滑的尺寸动画:
struct MainView: View { @State private var showPopover = false @State private var currentType: ObjectType = .basic var body: some View { HStack(spacing: 20) { Button("切换对象类型") { currentType = currentType == .basic ? .advanced : .basic } Button("打开创建面板") { showPopover.toggle() } .background( NSPopoverWrapper(isShown: $showPopover) { DynamicFormView(objectType: currentType) } ) } .padding() .frame(width: 400, height: 200) } }
核心要点
- 开启
NSPopover的animates属性,利用系统自带的尺寸动画 - 通过
NSHostingController监听内容视图的intrinsicContentSize,实时同步popover尺寸 - 协调器处理popover关闭后的状态同步,避免绑定值不一致
内容的提问来源于stack exchange,提问作者Rafael Bitencourt
相关产品推荐
相关产品推荐

