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

SwiftUI:ScrollView中第二个LazyVStack可见时如何隐藏BottomRoundedRectangle?

问题:SwiftUI中滚动到第二个LazyVStack时无法隐藏底部视图

我尝试在第二个LazyVStack显示在屏幕上时隐藏BottomRoundedRectangle,但功能失效,且.onPreferenceChange(OffsetKey.self)中的value值始终不变。相关代码如下:

struct OffsetKey: PreferenceKey {
    typealias Value = CGFloat

    static let defaultValue: Value = .zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}

struct OffsetKeyBackground: ViewModifier {
    func body(content: Content) -> some View {
        GeometryReader { reader in
            content
                .preference(
                    key: OffsetKey.self,
                    value: -reader.frame(in: .named("secondLazyStack")).origin.y
                )
        }
    }
}

struct ScrollQuestion: View {
    @State
    private var elementValue: CGFloat = .zero
    
    var body: some View {
        ScrollView(.vertical) {
            // Dynamic long text there
            LazyVStack {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.gray.opacity(0.3))
                    .frame(height: 920) // dynamic text, 920 for example, may be bigger
            }
            
            // 2nd LazyVStack
            LazyVStack {
                RoundedRectangle(cornerRadius: 12)
                    .frame(height: 820)
            }
            .coordinateSpace(name: "secondLazyStack")
        }
        .safeAreaInset(edge: .bottom) {
            // How to hide this correctly when we see second LazyVStack ?
            BottomRoundedRectangle()
//          .opacity(elementValue < 50 ? 1 : 0)
        }
        .modifier(OffsetKeyBackground())
        .onPreferenceChange(OffsetKey.self) { value in
            debugPrint(value)
            
            elementValue = value
        }
    }
}

struct BottomRoundedRectangle: View {
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8)
                .frame(height: 40)
                .foregroundStyle(.indigo)
                .padding(.horizontal)
            
            Text("Visible only in first LazyVStack")
                .foregroundStyle(.white)
        }
    }
}

#Preview {
    ScrollQuestion()
}

问题根源

  1. 偏好设置位置错误:把OffsetKeyBackground修饰在ScrollView外层,导致GeometryReader只能获取整个ScrollView的坐标,无法跟踪第二个LazyVStack的滚动位置。
  2. 坐标空间引用错误:使用第二个LazyVStack自身的坐标空间(.named("secondLazyStack")),其原点在自身左上角,无法正确计算它在屏幕中的偏移量。

修复方案

步骤1:调整偏好设置的挂载位置

将OffsetKeyBackground从ScrollView移到第二个LazyVStack上,让GeometryReader专门跟踪这个视图的位置。

步骤2:修正坐标空间计算

改用.global坐标空间获取第二个LazyVStack的顶部Y值,这样能准确判断它是否进入屏幕视野。

步骤3:调整隐藏逻辑

根据第二个LazyVStack的顶部Y值与屏幕高度的对比,决定是否隐藏底部视图。

修改后的完整代码

struct OffsetKey: PreferenceKey {
    typealias Value = CGFloat

    static let defaultValue: Value = .zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}

struct OffsetKeyBackground: ViewModifier {
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { reader in
                    Color.clear
                        .preference(
                            key: OffsetKey.self,
                            value: reader.frame(in: .global).minY
                        )
                }
            )
    }
}

struct ScrollQuestion: View {
    @State
    private var secondStackTopY: CGFloat = .zero
    private let screenHeight = UIScreen.main.bounds.height
    
    var body: some View {
        ScrollView(.vertical) {
            // 动态长文本区域
            LazyVStack {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.gray.opacity(0.3))
                    .frame(height: 920) // 示例高度,实际为动态内容
            }
            
            // 第二个LazyVStack
            LazyVStack {
                RoundedRectangle(cornerRadius: 12)
                    .frame(height: 820)
            }
            .modifier(OffsetKeyBackground())
        }
        .safeAreaInset(edge: .bottom) {
            BottomRoundedRectangle()
                // 当第二个LazyVStack顶部进入屏幕时隐藏
                .opacity(secondStackTopY > screenHeight ? 1 : 0)
        }
        .onPreferenceChange(OffsetKey.self) { value in
            debugPrint("第二个LazyVStack顶部Y值: \(value)")
            secondStackTopY = value
        }
    }
}

struct BottomRoundedRectangle: View {
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8)
                .frame(height: 40)
                .foregroundStyle(.indigo)
                .padding(.horizontal)
            
            Text("仅在第一个LazyVStack中可见")
                .foregroundStyle(.white)
        }
    }
}

#Preview {
    ScrollQuestion()
}

关键说明

  • 把偏好设置放在第二个LazyVStack的background里,确保GeometryReader实时跟踪该视图的全局位置。
  • 使用.global坐标空间获取的minY值代表视图顶部在屏幕中的位置,当该值小于屏幕高度时,说明视图已进入视野。
  • 用screenHeight作为判断阈值,适配不同设备的屏幕尺寸。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 20:45:12