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

Xcode 13中使用SwiftUI开发macOS应用如何调整窗口大小并固定宽高比

macOS SwiftUI 媒体播放器窗口比例锁定解决方案

核心思路

要实现加载视频后自动调整窗口大小、后续缩放保持固定宽高比,需要同时处理两部分逻辑:

  • 给播放视图添加比例约束,保证视图始终和视频比例一致
  • 主动操作AppKit层的NSWindow对象,设置窗口固定比例并触发初始尺寸调整,仅靠SwiftUI的frame修饰符无法实现窗口级的比例锁定

修正后完整代码

首先是数据模型PlayerModel的实现:

import AVFoundation
import SwiftUI
import UniformTypeIdentifiers

class PlayerModel: ObservableObject  {
    @Published var player: AVPlayer
    @Published var openFile = false
    // 存储当前视频的宽高比
    @Published var aspectRatio: CGFloat = 16/9
    // 窗口最小尺寸限制,可自行调整
    private let minWindowSize: CGSize = .init(width: 320, height: 180)
    
    init() {
        self.player = AVPlayer()
    }
    
    func loadAsset(from url: URL) {
        let asset = AVAsset(url: url)
        // 异步读取视频轨道的原始尺寸
        Task {
            guard let videoTrack = try? await asset.loadTracks(withMediaType: .video).first,
                  let naturalSize = try? await videoTrack.load(.naturalSize) else { return }
            let videoRatio = naturalSize.width / naturalSize.height
            DispatchQueue.main.async {
                self.aspectRatio = videoRatio
                // 调整窗口尺寸并锁定比例
                self.resizeWindowTo(videoSize: naturalSize)
                // 加载视频到播放器
                self.player.replaceCurrentItem(with: AVPlayerItem(asset: asset))
                self.player.play()
            }
        }
    }
    
    // 操作NSWindow实现窗口比例锁定和初始尺寸调整
    private func resizeWindowTo(videoSize: CGSize) {
        guard let currentWindow = NSApp.windows.first(where: { $0.isKeyWindow }) else { return }
        // 可选:限制初始窗口高度不超过屏幕可用高度,避免窗口超出显示范围
        let screenMaxHeight = NSScreen.main?.visibleFrame.height ?? 1080
        var targetSize = videoSize
        if targetSize.height > screenMaxHeight {
            targetSize.width *= screenMaxHeight / targetSize.height
            targetSize.height = screenMaxHeight
        }
        // 设置窗口内容比例,后续用户拖拽缩放会自动保持该比例
        currentWindow.contentAspectRatio = videoSize
        // 主动调整窗口到目标尺寸
        currentWindow.setContentSize(targetSize)
        // 设置窗口最小尺寸,避免缩放过小
        currentWindow.minSize = minWindowSize
    }
}

然后是视图ContentView的实现:

import SwiftUI
import AVKit

struct ContentView: View {
    @ObservedObject var playerModel : PlayerModel
    
    var body: some View {
        VideoPlayer(player: playerModel.player)
            .fileImporter(isPresented: $playerModel.openFile, allowedContentTypes: [.movie]) { res in
                do {
                    let fileUrl = try res.get()
                    // 沙盒环境下需要申请文件访问权限
                    guard fileUrl.startAccessingSecurityScopedResource() else { return }
                    playerModel.loadAsset(from: fileUrl)
                    fileUrl.stopAccessingSecurityScopedResource()
                } catch{
                    print ("文件读取错误: \(error.localizedDescription)")
                }
            }
            // 按视频比例约束视图,撑满窗口的同时保持比例
            .aspectRatio(playerModel.aspectRatio, contentMode: .fit)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
    }
}

注意事项

  • 采用SwiftUI App生命周期的项目,不要在WindowGroup上添加固定宽高的frame修饰符,否则会覆盖窗口的比例设置
  • 多窗口场景下可以通过NSViewRepresentable获取当前视图所在的窗口实例,替代NSApp.windows.first的写法,避免找错窗口

内容的提问来源于stack exchange,提问作者Philip Mok

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 18:15:02