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
相关产品推荐
相关产品推荐

