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
相关产品推荐
相关产品推荐

