恢复ScrollView内容位置

问题描述 投票:0回答:1

是否有某种方法可以恢复

ScrollView
内容位置而无需视觉“跳跃”? (例如在渲染之前设置 ScrollView 内容偏移量)。我的意思是上次在
ScrollView
消失/解除分配之前的确切内容位置。以某种方式保存并恢复
ScrollView
状态并提供用户上次看到的相同位置。

我尝试了一些方法来使用

GeometryReader
测量内容偏移并使用
ScrollViewProxy.scrollTo()
恢复,但它并不安全,根据 Apple 文档,可能会导致运行时错误和崩溃:

在执行内容视图生成器期间不得使用 ScrollViewProxy;这样做会导致运行时错误

struct Screen : View {
    
    var body: some View {
        ZStack {
            background
            VStack {
                frontWindow
                Spacer()
            }
        }
    }
    
    private var frontWindow: some View { FrontWindow() }
    
    private var background: some View { Color.black }
    
}

private struct FrontWindow : View {
    
    @State private var expanded = true
    @State private var items: [Int] = (0..<200000).map { $0 }
    
    var body: some View {
        VStack {
            HStack { Spacer(); button; Spacer() }
            if expanded { list }
        }
        .padding()
        .background(Color.white)
    }
    
    private var button: some View {
        Button { withAnimation { expanded.toggle() } } label: {
            HStack { 
                Spacer()
                Text(expanded ? "hide" : "expand")
                    .padding()
                    .background(Color.yellow)
                    .cornerRadius(8)
                Spacer()
            }
        }
    }
    
    private var list: some View {
        ScrollView {
            LazyVStack {
                ForEach(items, id: \.self) { item in
                    HStack { Spacer(); Text("\(item)"); Spacer() }
                        .padding()
                        .background(Color.gray)
                        .cornerRadius(8)
                        .padding()
                }
            }
        }
    }
    
}
swiftui scrollview
1个回答
0
投票

您可以利用

iOS 17+
新的滚动 API 来根据列表值跟踪内容偏移量。首先,声明一个
@State
变量,如下所示:

@State private var activeID: Int?

我使用 Int 因为您对项目使用

Int
,但您可以使用符合
Hashable
的任何对象。

然后,修改您的列表视图以使用新的 API:

private var list: some View {
    ScrollView {
        LazyVStack {
            ForEach(items, id: \.self) { item in
                HStack { Spacer(); Text("\(item)"); Spacer() }
                    .padding()
                    .background(Color.gray)
                    .cornerRadius(8)
                    .padding()
            }
        }
        /// This is mandatory in order to keep track of scroll content changes
        .scrollTargetLayout()
    }
    .scrollPosition(id: $activeID, anchor: .center)
    .onAppear {
        /// Set list content position here based on the value
        activeID = items.count/2
    }
    /// Keep track of any scroll
    .onChange(of: activeID) { oldValue, newValue in
        print("New Active ID: \(newValue ?? 0)")
    }
}

这里的新功能是

scrollTargetLayout
scrollPosition
修饰符。第一个告诉您要跟踪的内容是什么,第二个根据附加的视图
scrollTargetLayout
更新当前位置。

由于您还希望应用程序重新加载 ScrollView 的最后位置,因此您可以使用

AppStorage
属性包装器 来保存它:

@AppStorage("activeID") private var savedActiveID: Int?

并更新

onAppear
并添加
onDisappear

.onAppear {
    /// Set list content position here based on the value
    activeID = savedActiveID ?? items.count/2
}
.onDisappear {
    savedActiveID = activeID ?? 0
}

如果您想涵盖所有情况,也许使用

scenePhase
会更好。首先在View中声明场景阶段

@Environment(\.scenePhase) private var phase

然后使用

onChange
观察场景的变化(应用程序进入后台等...):

.onChange(of: phase) { oldValue, newValue in
        switch newValue {
        case .background:
            return
        case .inactive:
            savedActiveID = activeID ?? 0
        case .active:
            return
        @unknown default:
            return
        }
    }

希望您发现此解决方案有用。 让我知道这是否适合您!

© www.soinside.com 2019 - 2024. All rights reserved.