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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 18:15:01