SwiftUI ScrollView 内含 Button 无法检测滚动怎么办?是swiftui的bug吗?

import SwiftUI
struct ScrollOffsetPreferenceKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
struct test5Scroll: View {
    var body: some View {
        NavigationView {
            
            ScrollView {
                GeometryReader { proxy in
                    if let distanceFromTop = proxy.bounds(of: .named("MyScrollView"))?.minY {
                        Text(distanceFromTop, format: .number)
                            .padding(.top, 120)
                            .foregroundStyle(.pink)
                            .navigationTitle("明明变了为什么检测不到")
                            .preference(key: ScrollOffsetPreferenceKey.self, value: distanceFromTop)
                    } else {
                        Text("检测无效")
                    }
                }
                ForEach(0 ..< 30) { index in
                    // 很奇怪的是,如果你把外层的 HStack去掉,就能检测到滚动,否则检测不到
                    // 或者你把Button去掉直接用Text也是能检测到的?这是bug吗?怎么修复?
                    HStack{
                        Button(action: {
                            
                        }, label: {
                            Text("item --- \(index)")
                        })
                    }
                        .frame(width: 100, height: 40)
                }
            }
            .background(.black.opacity(0))
            .coordinateSpace(.named("MyScrollView"))
            .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                // 明明变了为什么滚动的时候检测不到?
                print("Scroll offset ----- \(value)")
            }
        }
        
    }
}

#Preview {
    test5Scroll()
}

在检测滚动的时候,当元素内有Button就检测不到滚动

异常情况正常情况

我尝试去除Button,可以检测到,但是不符合预期啊,谢谢了我该怎么办

阅读 428
avatarAI BotBETA

回答

这不是 SwiftUI 的 bug,而是由于 GeometryReader 在某些情况下对布局计算的影响导致的。当 GeometryReader 包裹的内容包含交互性视图(如 Button)时,它可能会干扰滚动视图的布局更新,导致无法正确捕捉到滚动偏移量。

为了解决这个问题,你可以尝试以下两种方法之一:

  1. 使用 VStackHStack 代替 GeometryReader
    你可以尝试去掉 GeometryReader,改用 VStackHStack 并通过其他方式获取滚动视图的位置。例如,可以使用 ScrollViewReaderLazyVStack 来获取滚动位置。
  2. 使用 PreferenceKeyGeometryEffect
    如果你需要精确获取滚动视图的位置,可以创建一个自定义的 GeometryEffect 来代替 GeometryReader。这种方法可以让你在不影响布局的情况下获取所需的几何信息。

下面是第一种方法的示例代码,使用 ScrollViewReaderLazyVStack 来获取滚动位置:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack {
                    ForEach(0 ..< 30) { index in
                        HStack {
                            Button(action: {
                                // Action for button
                            }) {
                                Text("Item --- \(index)")
                            }
                            .frame(width: 100, height: 40)
                        }
                    }
                }
                .padding(.horizontal)
                .background(
                    GeometryReader { proxy in
                        Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: -proxy.frame(in: .global).minY)
                    }
                )
            }
            .coordinateSpace(name: "MyScrollView")
            .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                print("Scroll offset ----- \(value)")
            }
            .navigationTitle("Scroll Detection")
        }
    }
}

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

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

在这个示例中,GeometryReader 被放置在一个 Color.clear.preference 中,它不会影响布局,但能够捕捉到滚动视图的位置。注意,这种方法可能需要根据你的具体需求进行调整。

1 个回答

使用自定义 ButtonStyle: 通过自定义 ButtonStyle,可以避免 Button 拦截触摸事件。

import SwiftUI

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

struct TransparentButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .contentShape(Rectangle())
            .background(Color.clear)
    }
}

struct test5Scroll: View {
    var body: some View {
        NavigationView {
            ScrollView {
                GeometryReader { proxy in
                    if let distanceFromTop = proxy.bounds(of: .named("MyScrollView"))?.minY {
                        Text(distanceFromTop, format: .number)
                            .padding(.top, 120)
                            .foregroundStyle(.pink)
                            .navigationTitle("明明变了为什么检测不到")
                            .preference(key: ScrollOffsetPreferenceKey.self, value: distanceFromTop)
                    } else {
                        Text("检测无效")
                    }
                }
                ForEach(0 ..< 30) { index in
                    HStack {
                        Button(action: {
                            // Button action
                        }) {
                            Text("item --- \(index)")
                        }
                        .buttonStyle(TransparentButtonStyle())
                    }
                    .frame(width: 100, height: 40)
                }
            }
            .background(Color.black.opacity(0))
            .coordinateSpace(.named("MyScrollView"))
            .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                print("Scroll offset ----- \(value)")
            }
        }
    }
}

#Preview {
    test5Scroll()
}

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏