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

iOS16下切换主题时SwiftUI Sheet尺寸变为0(疑似系统Bug)

iOS 16 切换主题导致自适应 Sheet 尺寸归零的解决方案

问题背景

通过 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 在主题切换时的失效问题:

  1. 实现尺寸读取器:
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)
        }
    }
}
  1. 修改自适应 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:37:32