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

iOS不同版本下GeometryReader计算Sheet高度结果不一致求助

修复iOS 18中Sheet内GeometryReader高度为0的问题,实现自适应高度不可拖拽Sheet

问题概述

需求:在iOS 16及以上版本中,使用presentationDetent(.height)结合GeometryReader计算内容高度,实现Sheet自适应内容,且第二个Sheet不可拖拽、保持计算后的固定尺寸。
问题:GeometryReader返回的高度存在版本差异——iPhone 15模拟器iOS 17.4环境下返回正确高度,iOS 18环境下返回0.0。
已尝试:Preference Key、将overlay替换为background、用task替代onAppear,均未解决。

核心原因

iOS 18调整了Sheet的布局触发时机:onAppear在Sheet内容尚未完成布局时就会触发,导致GeometryReader无法获取有效尺寸;而旧版本中onAppear触发时机晚于内容布局,能拿到正确高度。

可行修复方案

方案1:PreferenceKey结合异步更新布局

通过PreferenceKey捕获内容实际高度,在布局完成后异步更新Sheet的detent,同时隐藏拖拽指示器实现不可拖拽。

步骤1:定义PreferenceKey

struct ContentHeightPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

步骤2:修改Sheet实现

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel
    
    @State var sheetHeight: CGFloat = .zero
    @State var isSheetLayoutComplete = false // 标记内容布局是否完成
    
    var body: some View {
        Text("Parent view")
            .sheet(isPresented: isPresented(for: .sheetOne), content: {
                VStack {
                    Text("Sheet one")
                    Button(action: { viewModel.activeSheet = .sheetTwo}, label: { Text("Next")})
                }
            })
            .sheet(isPresented: isPresented(for: .sheetTwo), content: {
                VStack(spacing: 0) {
                    Text("Sheet two")
                    Text("Sheet two")
                }
                // 用background捕获内容尺寸,确保优先级低于内容布局
                .background {
                    GeometryReader { proxy in
                        Color.clear
                            .preference(key: ContentHeightPreferenceKey.self, value: proxy.size.height)
                    }
                }
                // 监听高度变化,异步更新避免布局循环
                .onPreferenceChange(ContentHeightPreferenceKey.self) { newHeight in
                    guard newHeight > 0 else { return }
                    sheetHeight = newHeight
                    DispatchQueue.main.async {
                        isSheetLayoutComplete = true
                    }
                }
                // 初始用.automatic让内容正常布局,完成后切换到固定高度
                .presentationDetents(isSheetLayoutComplete ? [.height(sheetHeight)] : [.automatic])
                // 隐藏拖拽指示器,禁止拖拽调整尺寸
                .presentationDragIndicator(.hidden)
            })
    }
}

方案2:使用自定义Detent(iOS 16.4+)

利用.custom detent直接在回调中返回计算后的高度,无需依赖额外状态标记,实现更简洁:

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel
    
    @State var sheetHeight: CGFloat = .zero
    
    var body: some View {
        Text("Parent view")
            .sheet(isPresented: isPresented(for: .sheetOne), content: {
                VStack {
                    Text("Sheet one")
                    Button(action: { viewModel.activeSheet = .sheetTwo}, label: { Text("Next")})
                }
            })
            .sheet(isPresented: isPresented(for: .sheetTwo), content: {
                VStack(spacing: 0) {
                    Text("Sheet two")
                    Text("Sheet two")
                }
                .background {
                    GeometryReader { proxy in
                        Color.clear
                            .onAppear {
                                sheetHeight = proxy.size.height
                            }
                    }
                }
                // 自定义detent,初始用临时高度避免0尺寸显示
                .presentationDetents([.custom { _ in
                    sheetHeight == 0 ? 100 : sheetHeight
                }])
                .presentationDragIndicator(.hidden)
            })
    }
}

关键注意点

  • 优先用background替代overlay:background的布局优先级低于内容,能确保GeometryReader捕获的是内容实际渲染尺寸。
  • 异步更新状态:避免直接在布局回调中修改detent触发布局循环。
  • 隐藏拖拽指示器+单一detent:两者结合可彻底禁止Sheet的拖拽调整行为。

内容的提问来源于stack exchange,提问作者victorgson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 09:27:32