Swift:如何检测macOS应用主视图尺寸变化并获取新尺寸?
在macOS Swift中检测视图尺寸变化并切换横竖屏视图
一、AppKit(传统ViewController方式)
1. 监听视图尺寸变化通知
通过注册NSView.frameDidChangeNotification,精准捕获主视图的尺寸变更:
import AppKit class MainViewController: NSViewController { private var lastIsLandscape: Bool? override func viewDidLoad() { super.viewDidLoad() // 注册视图尺寸变化通知 NotificationCenter.default.addObserver( self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: view ) } @objc private func viewFrameChanged(_ notification: Notification) { guard let mainView = notification.object as? NSView else { return } let viewSize = mainView.frame.size let isLandscape = viewSize.width > viewSize.height // 仅当横竖屏状态变化时切换视图,避免重复操作 guard lastIsLandscape != isLandscape else { return } lastIsLandscape = isLandscape switchMainView(for: isLandscape) } private func switchMainView(for isLandscape: Bool) { // 移除所有现有子视图 view.subviews.forEach { $0.removeFromSuperview() } // 创建对应方向的视图 let targetView: NSView = isLandscape ? LandscapeView() : PortraitView() targetView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(targetView) // 添加全屏约束 NSLayoutConstraint.activate([ targetView.leadingAnchor.constraint(equalTo: view.leadingAnchor), targetView.trailingAnchor.constraint(equalTo: view.trailingAnchor), targetView.topAnchor.constraint(equalTo: view.topAnchor), targetView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } deinit { // 移除通知监听,避免内存泄漏 NotificationCenter.default.removeObserver(self) } } // 示例横向/纵向视图类 class LandscapeView: NSView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.wantsLayer = true self.layer?.backgroundColor = NSColor.systemBlue.cgColor } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class PortraitView: NSView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.wantsLayer = true self.layer?.backgroundColor = NSColor.systemOrange.cgColor } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
2. 重写viewDidLayout方法
如果不需要全局通知,直接重写视图控制器的viewDidLayout方法,在布局更新时检测尺寸:
override func viewDidLayout() { super.viewDidLayout() let viewSize = view.frame.size let isLandscape = viewSize.width > viewSize.height if lastIsLandscape != isLandscape { lastIsLandscape = isLandscape switchMainView(for: isLandscape) } }
3. 监听窗口尺寸变化
若需要基于整个窗口的尺寸判断,可监听NSWindow.didResizeNotification:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(windowResized(_:)), name: NSWindow.didResizeNotification, object: view.window ) } @objc private func windowResized(_ notification: Notification) { guard let window = notification.object as? NSWindow else { return } let windowSize = window.frame.size let isLandscape = windowSize.width > windowSize.height if lastIsLandscape != isLandscape { lastIsLandscape = isLandscape switchMainView(for: isLandscape) } }
二、SwiftUI方式
1. 使用GeometryReader监听尺寸变化
通过GeometryReader实时获取视图尺寸,并在尺寸变更时切换视图:
import SwiftUI struct ContentView: View { @State private var isLandscape = false var body: some View { Group { if isLandscape { LandscapeSwiftUIView() } else { PortraitSwiftUIView() } } .background( GeometryReader { geo in Color.clear .onAppear { isLandscape = geo.size.width > geo.size.height } .onChange(of: geo.size) { newSize in isLandscape = newSize.width > newSize.height } } ) } } // 示例SwiftUI视图 struct LandscapeSwiftUIView: View { var body: some View { Color.blue .overlay(Text("横向视图").foregroundColor(.white)) } } struct PortraitSwiftUIView: View { var body: some View { Color.orange .overlay(Text("纵向视图").foregroundColor(.white)) } }
2. 监听窗口尺寸通知
通过监听NSView.frameDidChangeNotification获取窗口内容视图的尺寸变化:
struct ContentView: View { @State private var isLandscape = false var body: some View { Group { if isLandscape { LandscapeSwiftUIView() } else { PortraitSwiftUIView() } } .onReceive(NotificationCenter.default.publisher(for: NSView.frameDidChangeNotification, object: NSApp.mainWindow?.contentView)) { _ in guard let windowSize = NSApp.mainWindow?.frame.size else { return } isLandscape = windowSize.width > windowSize.height } } }
关键注意点
- 横竖屏判断逻辑:macOS没有系统级的横竖屏状态,通过窗口/视图的宽高比判断(宽>高为横向,反之为纵向)。
- 避免重复切换:记录上次的横竖屏状态,仅当状态变化时执行视图切换,防止布局频繁触发导致冗余操作。
内容的提问来源于stack exchange,提问作者Serhii Didanov
相关产品推荐
相关产品推荐

