SwiftUI中切换Segment Control时保持滚动位置的方案
解决SwiftUI分段切换时ScrollView滚动位置丢失问题
在你的代码中,切换Segment Control时未选中的视图会被SwiftUI销毁重建,导致滚动位置重置。以下两种方案可以实现类似UIKit中addChild/removeChild的状态保留效果:
方案一:保留视图实例(隐藏而非销毁)
直接让所有分段视图始终存在于视图层级中,通过透明度和交互控制来实现显示/隐藏,完全保留视图状态,最接近UIKit的子视图管理逻辑。
修改后的HomeListView代码:
struct HomeListView:View { @State private var favoriteColor = 0 var body: some View { VStack { Picker("What is your favorite color?", selection: $favoriteColor) { Text("Red").tag(0) Text("Green").tag(1) } .pickerStyle(.segmented) .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) // 用ZStack叠加所有视图,控制显示状态 ZStack { HomeViewListing() .opacity(favoriteColor == 0 ? 1 : 0) .allowsHitTesting(favoriteColor == 0) // 未选中时禁用交互 TestListingView() .opacity(favoriteColor == 1 ? 1 : 0) .allowsHitTesting(favoriteColor == 1) } } .navigationBarTitleDisplayMode(.inline) } }
优点:代码改动极小,无需额外状态管理,自动保留所有视图状态(包括滚动位置、内部控件状态)。
缺点:所有分段视图会同时加载,内存占用较高,适合简单视图或分段数量少的场景。
方案二:保存并恢复滚动位置
通过ObservableObject记录滚动位置,结合ScrollViewReader在视图重建时恢复位置,内存占用更低,适合复杂视图或多分段场景。
1. 创建滚动位置管理器
class ScrollPositionManager: ObservableObject { @Published var targetIndex: Int? // 保存需要恢复的滚动位置 }
2. 修改父视图传递管理器
struct HomeListView:View { @State private var favoriteColor = 0 // 为每个分段视图单独创建管理器 @StateObject private var homeScrollManager = ScrollPositionManager() @StateObject private var testScrollManager = ScrollPositionManager() var body: some View { VStack { Picker("What is your favorite color?", selection: $favoriteColor) { Text("Red").tag(0) Text("Green").tag(1) } .pickerStyle(.segmented) .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) selectedView } .navigationBarTitleDisplayMode(.inline) } @ViewBuilder var selectedView: some View { switch favoriteColor { case 0: HomeViewListing(scrollManager: homeScrollManager) default: TestListingView(scrollManager: testScrollManager) } } }
3. 修改子视图实现滚动记录与恢复
struct HomeViewListing:View { @ObservedObject var scrollManager: ScrollPositionManager var body: some View { ScrollView { ScrollViewReader { proxy in LazyVStack { ForEach(0..<100) { item in Text("\(item)") .id(item) // 为每个元素设置唯一ID,用于定位滚动 } } .onAppear { // 视图出现时恢复滚动位置 if let index = scrollManager.targetIndex { proxy.scrollTo(index, anchor: .top) } } } } .scrollIndicators(.hidden) // 监听滚动变化,记录当前显示的第一个元素 .onScrollChange { phase in guard let offsetY = phase.contentOffset.y else { return } // 假设每个Text高度约40,计算当前显示的元素索引 let currentIndex = Int(offsetY / 40) scrollManager.targetIndex = max(0, min(99, currentIndex)) } } } struct TestListingView:View { @ObservedObject var scrollManager: ScrollPositionManager var body: some View { ScrollView { ScrollViewReader { proxy in LazyVStack { ForEach(0..<100) { item in Text("\(item)") .id(item) } } .onAppear { if let index = scrollManager.targetIndex { proxy.scrollTo(index, anchor: .top) } } } } .scrollIndicators(.hidden) .onScrollChange { phase in guard let offsetY = phase.contentOffset.y else { return } let currentIndex = Int(offsetY / 40) scrollManager.targetIndex = max(0, min(99, currentIndex)) } } }
优点:仅保存必要的滚动状态,内存占用低,支持大量分段或复杂视图。
缺点:需要手动处理滚动位置的计算与恢复,代码量稍大。
内容的提问来源于stack exchange,提问作者Ishteyaque Ahmad
相关产品推荐
相关产品推荐

