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

SwiftUI内容动态变更时如何实现NSWindow窗口大小的平滑动画

SwiftUI视图动画正常但窗口直接跳变到最终尺寸的效果演示

解决方案

出现该问题的核心原因是SwiftUI的内容动画默认仅在SwiftUI渲染层执行,NSWindow的自动尺寸适配逻辑没有关联SwiftUI的动画事务,只会捕获内容最终尺寸直接跳转。

方案1:全场景通用适配(支持SwiftUI生命周期)

通过NSViewRepresentable桥接获取当前视图所在的NSWindow,绑定SwiftUI动画事务同步触发窗口尺寸动画,无需手动管理Window实例。

首先添加工具扩展:

import AppKit
import SwiftUI

extension NSView {
    /// 获取当前NSView所属的NSWindow
    var hostWindow: NSWindow? {
        var next = nextResponder
        while let responder = next {
            if let window = responder as? NSWindow {
                return window
            }
            next = responder.nextResponder
        }
        return nil
    }
}

extension View {
    /// 绑定当前视图的动画事务,同步触发所属NSWindow的尺寸动画
    func syncWindowSizeAnimation() -> some View {
        background(WindowAnimationSyncView())
    }
}

private struct WindowAnimationSyncView: NSViewRepresentable {
    func makeNSView(context: Context) -> NSView {
        NSView(frame: .zero)
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {
        guard let window = nsView.hostWindow,
              let animation = context.transaction.animation else { return }
        // 同步SwiftUI动画参数到AppKit动画上下文
        NSAnimationContext.runAnimationGroup { ctx in
            ctx.duration = animation.duration
            ctx.timingFunction = animation.timingFunction as? CAMediaTimingFunction
            ctx.allowsImplicitAnimation = true
            // 用animator对象触发窗口尺寸渐变
            window.animator().setContentSize(window.contentView?.fittingSize ?? .zero)
        }
    }
}

使用方式非常简单,只需在你的根视图上添加syncWindowSizeAnimation()修饰符即可:

struct ContentView: View {
    @State var isExpanded = false
    
    var body: some View {
        VStack {
            Button(isExpanded ? "Close" : "Open", action: toggleExpand)
            
            if isExpanded {
                ForEach(0..<5) { count in
                    Text("\(count). Some Item")
                }
            }
        }
        .padding()
        .fixedSize()
        .syncWindowSizeAnimation() // 仅需添加这一行
    }
    
    func toggleExpand() {
        withAnimation(.easeInOut(duration: 1)) { isExpanded.toggle() }
    }
}

方案2:手动创建NSWindow场景适配

如果你是通过传统AppKit方式手动创建NSWindow,直接配置Window属性并监听尺寸变更即可:

// 创建Window时配置动画属性
let window = NSWindow(contentRect: .zero, styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false)
window.animationBehavior = .documentWindow
window.contentView?.postsFrameChangedNotifications = true

// 监听内容视图尺寸变更
NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: window.contentView, queue: .main) { _ in
    NSAnimationContext.runAnimationGroup { ctx in
        ctx.duration = 1
        ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        window.animator().setContentSize(window.contentView?.fittingSize ?? .zero)
    }
}

注意事项

  • 提前配置窗口的minSize和maxSize,确保动画过程中的中间尺寸不会超出限制,避免动画卡顿
  • 不要在用户手动拖拽窗口边缘调整尺寸的同时触发该动画,避免事件冲突
  • 该方案兼容macOS 12及以上系统,更低版本可将context.transaction相关逻辑替换为手动传入动画参数即可

内容的提问来源于stack exchange,提问作者Martin Powlette Jr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 01:39:03