SwiftUI ScrollViewReader сохраняет позицию прокрутки при изменении размера
При использовании ScrollViewReader для прокрутки до определенной позиции, например, до 40% ширины - как вы можете сохранить позицию прокрутки при изменении размера содержимого ScrollView?
Я написал небольшой пример приложения, чтобы проиллюстрировать проблему:
Первоначально ширина дочернего элемента ScrollView равна 1600, а позиция прокрутки - 40%. Когда вы нажимаете кнопку «Изменить ширину на 800», ширина дочернего элемента изменяется на 800, а ScrollView прокручивается до конца. Я хотел бы, чтобы ScrollView сохранял положение прокрутки при изменении размера или всегда прокручивал до 40% после изменения размера.
struct ContentView: View {
@State private var relativeScrollPosition: Double?
@State private var childWidth: CGFloat = 1600
var body: some View {
VStack {
Button("Change width to 800") {
childWidth = 800
}
Button("Change width to 1600") {
childWidth = 1600
}
RelativeScrollView(
relativeScrollPosition: $relativeScrollPosition,
childWidth: childWidth
) {
HStack{
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("1")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("2")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("3")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("4")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("5")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("6")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("7")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("8")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("9")
}
ZStack {
Spacer().frame(height: 100).background(Color.green)
Text("10")
}
}
.frame(width: childWidth, height: 100, alignment: .center)
}
.onAppear {
scrollTo40percent()
}
.onChange(of: childWidth, perform: { _ in
scrollTo40percent()
})
}
}
private func scrollTo40percent() {
relativeScrollPosition = 0.4
}
}
struct RelativeScrollView<Content: View>: View {
@Binding var relativeScrollPosition: Double?
let childWidth: CGFloat
let isAnimating = true
var child: () -> Content
var body: some View {
ScrollViewReader { reader in
ScrollView(.horizontal) {
ZStack {
HStack {
// swiftlint:disable identifier_name
ForEach(0..<101) { i in
Spacer().id(i)
}
}
.frame(width: childWidth)
self.child()
}
}
.onAppear {
scroll(reader, to: relativeScrollPosition)
}
.onChange(of: relativeScrollPosition) { newPos in
scroll(reader, to: newPos)
}
}
}
private func scroll(_ reader: ScrollViewProxy, to position: Double?) {
guard let unwrappedPosition = position else { return }
assert(unwrappedPosition >= 0 && unwrappedPosition <= 1)
let elementToScrollTo = Int(unwrappedPosition * 100)
if isAnimating {
withAnimation {
reader.scrollTo(elementToScrollTo, anchor: .center)
}
} else {
reader.scrollTo(elementToScrollTo, anchor: .center)
}
}
}