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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 08:12:57