是否有某种方法可以恢复
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()
}
}
}
}
}
您可以利用
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
}
}
希望您发现此解决方案有用。 让我知道这是否适合您!