SwiftUI中如何判断视图是在Popover还是Sheet中呈现
解决方案
horizontalSizeClass判断失效的核心原因是:Popover弹出后,系统会自动为Popover内的视图层级设置独立的compact宽度尺寸类,和触发弹出的父视图层级的尺寸类不一致,在被弹出视图内部读取该值,自然无法准确匹配系统选择的展示样式。
不要在被弹出的视图内部猜测当前展示形式,最稳定的实现是在弹出入口处,基于父视图层级的尺寸类(系统本身就是依据这个值决定用Popover还是Sheet的),明确给被弹出视图传递展示类型标记。
具体实现步骤
- 自定义环境值,用于全局传递展示类型标记,避免多层级视图层层传参的麻烦:
private enum PresentedAsSheetEnvironmentKey: EnvironmentKey { static let defaultValue = false } extension EnvironmentValues { var isPresentedAsSheet: Bool { get { self[PresentedAsSheetEnvironmentKey.self] } set { self[PresentedAsSheetEnvironmentKey.self] = newValue } } }
- 修改
SettingsView逻辑,直接从环境中读取展示标记判断是否显示Done按钮,不再依赖视图内部的尺寸类值:
struct SettingsView: View { fileprivate let internalSettings = SettingsViewInternal() @Environment(\.dismiss) var dismiss @Environment(\.isPresentedAsSheet) var isPresentedAsSheet var body: some View { NavigationStack { internalSettings .navigationTitle("Settings") .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Reset") { internalSettings.reset() } } if isPresentedAsSheet { ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } } } } } } }
- 在触发弹出的父视图中,读取父视图层级自身的
horizontalSizeClass,弹出时给被弹出视图注入对应环境值:
struct AnchorView: View { // 这里读取的是父视图层级的尺寸类,和系统选择弹出样式的判断依据完全一致 @Environment(\.horizontalSizeClass) private var parentSizeClass @State private var settingsPresented = false var body: some View { Button { settingsPresented = true } label: { Image("gears") } .popover(isPresented: $settingsPresented) { SettingsView() // 父级尺寸类为compact时系统会自动将Popover降级为Sheet展示 .environment(\.isPresentedAsSheet, parentSizeClass == .compact) } } }
注意事项
- 该方案兼容所有iOS版本,不受系统Popover内部实现调整影响:iOS 15及以下系统的
.popover在compact宽度下会自动降级为Sheet样式,iOS 16+的.presentation自适应弹出逻辑也同样基于父层级尺寸类判断,判断逻辑和系统行为完全匹配。 - 不要尝试在被弹出视图内部通过遍历视图层级、读取UIViewController的
modalPresentationStyle来判断,不同系统版本的Popover内部视图层级结构有差异,会出现兼容问题。
内容的提问来源于stack exchange,提问作者Léo Natan
相关产品推荐
相关产品推荐

