iOS16下切换主题时SwiftUI Sheet尺寸变为0(疑似系统Bug)
问题背景
通过 PreferenceKey + GeometryReader 实现的 Sheet 内容自适应尺寸功能,在 iOS 16 版本中切换系统主题(使用 overrideUserInterfaceStyle)时会出现异常:Sheet 尺寸直接变为 0,且 onPreferenceChange 回调完全不触发。该问题在 iOS 18.1 中已修复,但需要兼容 iOS 16 的场景下必须针对性处理。
核心原因
iOS 16 中,主题切换时 Sheet 的视图层级会被重置,导致 GeometryReader 无法正确计算内容尺寸,PreferenceKey 的值传递链路中断,最终 contentSize 保持初始的 .zero,使得 presentationDetents 设置的高度为 0。
可行解决方案
方案1:通过 id 修饰符强制 Sheet 内容重建
在主题切换时,给 Sheet 内容添加绑定到主题状态的 id,让视图完全重建,触发 GeometryReader 重新计算尺寸:
修改 ContentView 中的 Sheet 调用代码:
.bottomSheet(isPresented: $showSheet, content: { ThemeSettingsSheet(isDarkMode: isDarkMode) .id(isDarkMode) // 主题切换时触发视图重建 })
此方案简单直接,能快速修复 iOS 16 的问题,且对其他版本无副作用。
方案2:改用 UIKit 固有尺寸获取逻辑
通过 UIViewRepresentable 直接获取内容的 intrinsicContentSize,绕过 SwiftUI PreferenceKey 在主题切换时的失效问题:
- 实现尺寸读取器:
struct IntrinsicSizeReader: UIViewRepresentable { var onChange: (CGSize) -> Void func makeUIView(context: Context) -> UIView { let view = UIView() view.backgroundColor = .clear return view } func updateUIView(_ uiView: UIView, context: Context) { DispatchQueue.main.async { onChange(uiView.intrinsicContentSize) } } }
- 修改自适应 Detent 修饰器:
fileprivate struct FittedPresentationDetentModifier: ViewModifier { @State private var contentSize: CGSize = CGSize(width: UIScreen.main.bounds.width, height: 60) func body(content: Content) -> some View { content .background(IntrinsicSizeReader(onChange: { size in guard size.height > 0 else { return } contentSize = size })) .presentationDetents([.height(contentSize.height)]) .presentationCornerRadius(32.0) } }
此方案依赖 UIKit 的固有尺寸计算,在 iOS 16 中稳定性更强。
方案3:保留历史尺寸避免归零
修改 onPreferenceChange 逻辑,当新尺寸为 0 时不更新,同时给 contentSize 设置最小初始高度:
fileprivate struct FittedPresentationDetentModifier: ViewModifier { // 设置最小初始高度,避免初始状态尺寸为0 @State private var contentSize: CGSize = CGSize(width: UIScreen.main.bounds.width, height: 60) func body(content: Content) -> some View { content .overlay( GeometryReader { proxy in Color.clear.preference( key: IntrinsicContentSizePreferenceKey.self, value: proxy.size ) } ) .onPreferenceChange(IntrinsicContentSizePreferenceKey.self) { newSize in // 仅当新尺寸有效时更新,主题切换时保留历史尺寸 if newSize.height > 0 { contentSize = newSize } } .presentationDetents([.height(contentSize.height)]) .presentationCornerRadius(32.0) } }
此方案无需修改视图结构,仅通过逻辑优化避免尺寸归零。
推荐方案
优先选择方案1,实现成本最低,且能彻底解决 iOS 16 下的问题,同时兼容其他版本。
内容的提问来源于stack exchange,提问作者fikricanc

