SwiftUI ScrollView实现滚动至上一个/下一个条目的功能问询
实现滚动到上一个/下一个条目的解决方案
可以实现这个功能,核心是通过跟踪当前第一个可见条目,结合ScrollViewReader(或iOS17+的scrollPosition)完成精准滚动。以下是完整实现代码及关键说明:
import SwiftUI import PlaygroundSupport // 用于收集条目位置信息的PreferenceKey struct VisibleItemPreferenceKey: PreferenceKey { static var defaultValue: [Int: CGRect] = [:] static func reduce(value: inout [Int: CGRect], nextValue: () -> [Int: CGRect]) { value.merge(nextValue()) { $1 } } } struct ContentView: View { @State var items = 1...100 @State private var currentFirstVisibleItem: Int = 1 @State private var scrollViewFrame: CGRect = .zero var body: some View { VStack { HStack { Button("Scroll to previous") { guard items.contains(currentFirstVisibleItem - 1) else { return } currentFirstVisibleItem -= 1 } Button("Scroll to next") { guard items.contains(currentFirstVisibleItem + 1) else { return } currentFirstVisibleItem += 1 } } .padding() // iOS17+ 用scrollPosition简化滚动 ScrollView(.vertical) { LazyVStack(alignment: .leading, spacing: 16) { ForEach(items, id: \.self) { item in Text(String(item)) .frame(width: 50, height: 50) .background(Color.gray.opacity(0.2)) .background( GeometryReader { geo in Color.clear .preference( key: VisibleItemPreferenceKey.self, value: [item: geo.frame(in: .named("scrollView"))] ) } ) } } } .frame(height: 300) .coordinateSpace(name: "scrollView") .background( GeometryReader { geo in Color.clear .onAppear { scrollViewFrame = geo.frame(in: .global) } .onChange(of: geo.frame(in: .global)) { scrollViewFrame = $0 } } ) .onPreferenceChange(VisibleItemPreferenceKey.self) { preferences in // 筛选可见条目并找到最顶部的一个 let visibleItems = preferences.filter { _, frame in scrollViewFrame.intersects(frame) } .sorted { $0.value.minY < $1.value.minY } if let firstVisible = visibleItems.first?.key { currentFirstVisibleItem = firstVisible } } .scrollPosition(id: $currentFirstVisibleItem) // iOS16及以下替换为ScrollViewReader写法: /* ScrollViewReader { proxy in ScrollView(.vertical) { LazyVStack(alignment: .leading, spacing: 16) { // 原有条目内容不变 } } // 其他监听逻辑不变 .onChange(of: currentFirstVisibleItem) { item in proxy.scrollTo(item, anchor: .top) } } */ } } } PlaygroundPage.current.setLiveView(ContentView())
关键实现逻辑
- 跟踪条目位置:通过自定义
VisibleItemPreferenceKey,用GeometryReader收集每个条目在ScrollView坐标系内的位置,实时更新位置数据。 - 识别第一个可见条目:监听位置数据变化,筛选出和ScrollView可见区域相交的条目,按Y轴坐标排序后取最顶部的条目ID,更新
currentFirstVisibleItem状态。 - 控制滚动:按钮点击时,基于当前第一个可见条目ID计算上一个/下一个条目ID,通过
scrollPosition(或ScrollViewReader.scrollTo)触发滚动,确保目标条目滚动到顶部位置。
这个方案支持手动滚动后自动更新基准条目,完全符合需求。
内容的提问来源于stack exchange,提问作者Niklas
相关产品推荐
相关产品推荐

