ScrollViewReader,如何读取当前行?

Bob*_*Bob 3 scrollview swiftui scrollviewreader

我正在寻找一种简洁的方法来识别 ScrollView 中当前可见的顶行。我希望 NavigationTitle 显示当前行号。有没有办法用 ScrollViewReader 来做到这一点,或者我必须用 GeometryReader 做些什么?在下面的代码中,我希望 myRowNumber 在用户上下滚动时更新。提前致谢。

struct ContentView: View {
    
    let myArray: [Int] = [Int](1...100)
    @State private var myRowNumber: Int = 50
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack{
                    ScrollViewReader { proxy in
                        ForEach(myArray, id: \.self) { index in
                            Text("Row \(index)").id(index).font(.title)
                        }
                        .onAppear {
                            proxy.scrollTo(50, anchor: .top)
                        }
                    }
                }
            }
            .navigationTitle("Current row = \(myRowNumber)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

rob*_*off 6

更新

如果您的部署目标是 iOS 17(或 macOS 14 等)或更高版本,您可以使用修饰符scrollPosition(id:)跟踪滚动视图的顶部可见行。

原来的

所以你想要这样的东西:

显示数字行的滚动视图。 第 50 行位于顶部。 滚动视图上方的标题显示“当前行 = 50”。 我滚动视图,标题会实时更改,以始终显示顶部可见行的行号。

SwiftUI 不提供直接读取顶行的方法,因此我们必须使用其他工具来计算它。

我们需要知道每一行相对于滚动视图顶部的位置。这意味着两件事:获取Anchor每一行的 ,并计算该行Anchor相对于 顶部的y 坐标ScrollView

我们可以Anchor使用anchorPreference修饰符收集 s,但首先我们需要创建一个PreferenceKey类型来管理集合。

struct AnchorsKey: PreferenceKey {
    // Each key is a row index. The corresponding value is the
    // .center anchor of that row.
    typealias Value = [Int: Anchor<CGPoint>]

    static var defaultValue: Value { [:] }

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value.merge(nextValue()) { $1 }
    }
}
Run Code Online (Sandbox Code Playgroud)

要将 an 变成Anchor<CGPoint>实际的CGPoint,我们需要 a GeometryProxy。假设我们有一个代理,我们希望从 y 坐标至少为零的行中选择 y 坐标最小的行。

private func topRow(of anchors: AnchorsKey.Value, in proxy: GeometryProxy) -> Int? {
    var yBest = CGFloat.infinity
    var answer: Int? = nil
    for (row, anchor) in anchors {
        let y = proxy[anchor].y
        guard y >= 0, y < yBest else { continue }
        answer = row
        yBest = y
    }
    return answer
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要将 aGeometryReader包裹起来ScrollView以获取 a GeometryProxy,并.overlayPreferenceValue在 the 中使用修饰符GeometryReader来访问收集的Anchors。

struct ContentView: View {
    let myArray: [Int] = [Int](1...100)
    @State private var myRowNumber: Int = 50

    var body: some View {
        NavigationView {
            GeometryReader { proxy in
                ScrollView {
                    LazyVStack{
                        ScrollViewReader { proxy in
                            ForEach(myArray, id: \.self) { index in
                                Text("Row \(index)").id(index).font(.title)
                                    .anchorPreference(
                                        key: AnchorsKey.self,
                                        value: .center
                                    ) { [index: $0] }
                            }
                            .onAppear {
                                proxy.scrollTo(50, anchor: .top)
                            }
                        }
                    }
                }
                .overlayPreferenceValue(AnchorsKey.self) { anchors in
                    let i = topRow(of: anchors, in: proxy) ?? -1
                    Color.clear
                        .navigationTitle("Current row = \(i)")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • [SwiftUI 实验室](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/) 有一系列有关 SwiftUI 首选项系统的文章。 (3认同)