I have the following scrubber implementation in SwiftUI. The +
button is supposed to move the ScrollView
up by 1 tick (or scrollPosition
incremented by 1) but the issue is no scrolling happens until I click 8-9 times. Is this a bug in iOS or a programming error?
struct BrokenVerticalScrubberDemo: View {
@State private var scrollPosition: Int? = 0
@State private var count: Int = 20
var body: some View {
VStack {
Text("Scroll Position: \(scrollPosition ?? 0)")
.font(.headline)
ScrollView(.vertical, showsIndicators: true) {
VStack(spacing: 8) {
ForEach(0..<count, id: \.self) { index in
Text("Tick \(index)")
.frame(height: 30)
.id(index)
}
}
.scrollTargetLayout()
.padding(.vertical, 50)
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $scrollPosition)
.frame(height: 300)
.border(Color.gray)
Button("+1") {
withAnimation {
scrollPosition = min((scrollPosition ?? 0) + 1, count - 1)
}
}
.padding(.top)
}
.padding()
}
}
#Preview {
BrokenVerticalScrubberDemo()
}
In contrast, if I use ScrollViewReader
as a workaround, it starts scrolling after 2 '+' button taps.
import SwiftUI
struct SomeWhatWorkingVerticalScrubberDemo: View {
@State private var scrollPosition: Int = 0
@State private var count: Int = 20
var body: some View {
VStack {
Text("Scroll Position: \(scrollPosition)")
.font(.headline)
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scrollViewProxy in
VStack(spacing: 8) {
ForEach(0..<count, id: \.self) { index in
Text("Tick \(index)")
.frame(height: 30)
.id(index)
}
}
.padding(.vertical, 50)
.onChange(of: scrollPosition) { newPosition in
withAnimation {
scrollViewProxy.scrollTo(newPosition, anchor: .center)
}
}
}
}
.frame(height: 300)
.border(Color.gray)
Button("+1") {
scrollPosition = min(scrollPosition + 1, count - 1)
}
.padding(.top)
}
.padding()
}
}
#Preview {
SomeWhatWorkingVerticalScrubberDemo()
}
Please try to use top anchor
.scrollPosition(id: $scrollPosition, anchor: .top)
on the scroll position.
The documentation about the default anchor behaviour is a bit cryptic to me. But in your example it seems to be the bottom anchor.
/// If no anchor has been provided, SwiftUI will scroll the minimal amount
/// when using the scroll position to programmatically scroll to a view.