You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 20:22:26