macOS27 上可变界面尺寸 iOS App 揭秘

把一个 iOS App 放到 Mac 上运行,拖动窗口边缘,变的只有宽高吗?

在 macOS 27 上做了一次小实验,答案颇有意味:随着用户调整窗口大小,SwiftUI 环境中的 Size Class 也会发生变化。原本常在横竖屏适配中见到的 compact 和 regular,如今在鼠标拖动之间,有了更直观的表现。

这件事值得留意。对于沿用固定设备假设的界面,一次再普通不过的窗口缩放,就可能让布局捉襟见肘。

说起 Size Class,许多开发者会条件反射地想到"iPhone 用 compact,iPad 用 regular"。这套经验便于入门,却容易先入为主。

SwiftUI 通过两个环境值描述水平和垂直方向的空间分类:

swift 复制代码
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Environment(\.verticalSizeClass) private var verticalSizeClass

两者的取值可以是 .compact、.regular,也可能为 nil。水平对应宽度,垂直对应高度。它们描述的是视图所处的布局环境,不能简单当成设备型号的别名。相关定义可见 Apple 的 UserInterfaceSizeClass 文档。

因此,看见一个宽窗口,便断言两个方向都是 regular,难免以偏概全。窗口的宽与高是两回事,两个方向的分类也应分别观察。

这次实验的做法很直接:让界面同时显示实际可用尺寸与两个 Size Class,然后拖动窗口,观察数值。核心代码并不复杂:

swift 复制代码
struct SizeClassProbe: View {
    @Environment(\.horizontalSizeClass) private var horizontal
    @Environment(\.verticalSizeClass) private var vertical

    var body: some View {
        GeometryReader { proxy in
            VStack(alignment: .leading, spacing: 12) {
                Text("宽:\(proxy.size.width, specifier: "%.1f") pt")
                Text("高:\(proxy.size.height, specifier: "%.1f") pt")
                Text("水平:\(name(horizontal))")
                Text("垂直:\(name(vertical))")
            }
            .padding()
        }
    }

    private func name(_ value: UserInterfaceSizeClass?) -> String {
        switch value {
        case .compact: "compact"
        case .regular: "regular"
        case nil: "nil"
        @unknown default: "unknown"
        }
    }
}

这里有个细节不能张冠李戴:GeometryReader 测量的是当前视图获得的布局空间,单位为点;它既不是显示器分辨率,也不是包含标题栏的窗口外框尺寸。将它与 Size Class 并排展示,恰好可以对照"具体尺寸"和"空间分类"这两个层次。

测试入口同样要选对。在 Xcode 中,应选择 My Mac (Designed for iPad) ,运行 iOS App 的 Mac 版本。多平台项目里的普通 My Mac 则可能启动原生 macOS App。两者虽然都出现在 Mac 桌面上,观察结果却不能混为一谈。

在本次 macOS 27 实测中,拖动 iOS App 窗口,确实能够观察到 Size Class 随之变化。这个结果说明:至少在当前测试环境下,分类会随着窗口调整而更新,不能将启动时读到的值视作一成不变。

不过,窗口尺寸连续变化,Size Class 只有少量离散状态。拖动了一段距离,数值变了,分类保持原样,完全可能。此次观察也不足以归纳出适用于所有设备、系统版本和运行方式的固定阈值。把某次测得的宽度刻进代码,反倒容易刻舟求剑。

对 SwiftUI 开发者而言,接下来便顺理成章:让布局持续读取环境,在分类变化时重新组织内容。例如,在水平空间宽裕时并排展示两块内容,在紧凑时改为上下排列:

swift 复制代码
struct AdaptiveContent: View {
    @Environment(\.horizontalSizeClass) private var horizontal

    private var layout: AnyLayout {
        horizontal == .regular
            ? AnyLayout(HStackLayout(alignment: .top, spacing: 20))
            : AnyLayout(VStackLayout(alignment: .leading, spacing: 20))
    }

    var body: some View {
        layout {
            GroupBox("概览") {
                Text("主要信息")
            }

            GroupBox("详情") {
                Text("补充内容")
            }
        }
        .padding()
    }
}

代码不必监听鼠标拖动,也不必把 Size Class 复制到一份额外的状态中。环境值更新后,SwiftUI 会重新求值,布局自然跟着调整。这里将 nil 一并落入纵向布局,是示例明确选择的回退策略。

当然,Size Class 也不能包办所有适配。两块内容是否真的放得下,还受文字长度、字体大小和容器空间影响。决定界面的整体组织方式,可以参考 Size Class;判断一组按钮能否并排容纳,则更适合交给 ViewThatFits 或具体的布局计算。各司其职,才能游刃有余。

macOS 27 上的这次实验,把一个老道理摆到了眼前:用户手中的窗口,随时都可能改变界面的空间条件。

我们写下的是布局规则,用户决定的是窗口大小。让界面懂得因地制宜,拖动窗口时的从容,便会水到渠成。

感谢宝子们的观赏,我们下次再会吧!8-)

相关推荐
tangzzzfan1 小时前
把 ADB 装进 macOS app
macos
大熊猫侯佩1 小时前
iPhone Duo 从外屏到内屏:ArrangementView 抢先适配
swiftui·xcode·apple
用户2181697049301 小时前
LLVM
ios
大熊猫侯佩1 小时前
为 iPhone Duo 做准备:containerConcentric 自适配指南
前端·swiftui·xcode
zeqinjie1 小时前
Flutter 获取 iPhone Duo 预留区位置
前端·flutter·ios
二流小码农1 小时前
鸿蒙开发:ArrayList,可不会让UI更新哦
android·ios·harmonyos
茶底世界之下6 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
2501_916007476 天前
苹果商店iOS应用上架费用全面解析:开发者计划与审核费用计算
android·ios·小程序·https·uni-app·iphone·webview
2501_916008896 天前
如何实现iOS App代码混淆:SwiftShield使用指南
android·ios·小程序·https·uni-app·iphone·webview