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

SwiftUI:如何通过ScrollViewReader获取ScrollView当前顶部可见行?

解决方案:用GeometryReader结合PreferenceKey实现需求

ScrollViewReader没法直接检测当前可见的顶部行——它的核心作用是通过ID定位并滚动到指定视图,没有提供获取可见区域元素的API。要实现你要的功能,必须结合GeometryReader + PreferenceKey,这是SwiftUI中追踪视图位置、识别可见区域的标准简洁方案。

下面是修改后的完整代码,关键改动点已标注:

import SwiftUI

// 1. 定义PreferenceKey,用来传递每行的顶部坐标和行号
struct RowOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: [Int: CGFloat] = [:]
    
    static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
        value.merge(nextValue()) { $1 }
    }
}

struct ContentView: View {
    let myArray: [Int] = [Int](1...100)
    @State private var myRowNumber: Int = 50
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack {
                    ForEach(myArray, id: \.self) { index in
                        Text("Row \(index)")
                            .font(.title)
                            // 2. 给每行添加GeometryReader,获取全局坐标并传递给PreferenceKey
                            .background(
                                GeometryReader { geo in
                                    Color.clear
                                        .preference(
                                            key: RowOffsetPreferenceKey.self,
                                            value: [index: geo.frame(in: .global).minY]
                                        )
                                }
                            )
                    }
                }
                // 3. 监听Preference变化,计算当前顶部可见行
                .onPreferenceChange(RowOffsetPreferenceKey.self) { offsets in
                    // 计算ScrollView的顶部临界位置:屏幕高度减去导航栏+安全区高度
                    let scrollViewTop = UIScreen.main.bounds.height - (UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) - 44
                    // 筛选出顶部坐标小于临界值的行,取最大的行号(最靠近顶部的可见行)
                    if let topRow = offsets.filter({ $0.value < scrollViewTop }).keys.max() {
                        myRowNumber = topRow
                    }
                }
                // 保留初始滚动到第50行的逻辑,可结合ScrollViewReader实现
                .overlay(
                    ScrollViewReader { proxy in
                        Color.clear
                            .onAppear {
                                proxy.scrollTo(50, anchor: .top)
                            }
                    }
                )
            }
            .navigationTitle("Current row = \(myRowNumber)")
        }
    }
}

关键逻辑说明:

  • PreferenceKey:作为数据传递的桥梁,收集所有行的ID与对应的全局顶部坐标。
  • GeometryReader:给每行添加透明背景,获取该行在全局坐标系中的位置,通过preference把数据传递给父视图。
  • onPreferenceChange:监听所有行的位置变化,计算ScrollView的顶部临界值,筛选出符合条件的行,更新当前顶部行号。

如果你需要初始滚动到指定行的功能,仍可结合ScrollViewReader实现,这和顶部行检测逻辑完全不冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 19:20:27