ScrollViewReader doesn't seem to work reliably with an animated alignmentGuide view as the target. If I scroll by hand then SVR will track the animated guide. Rescaling the content often breaks the behavior and requires another manual scroll to reconnect the animation to scrolling.
In my test I have a media player-like timeline. I'd like to keep the elapsed time cursor centered, no matter the length of the timeline rectangle.
struct ContentView: View {
@State private var zoom: Double = 10
let start = Date.now
let duration: TimeInterval = 60
var body: some View {
VStack(alignment: .leading) {
TimelineView(.periodic(from: start, by: 1)) { context in
let elapsed = context.date.timeIntervalSince(start)
ScrollViewReader { proxy in
ScrollView(.horizontal) {
Text("\(elapsed.formatted(.number.precision(.fractionLength(0)))) / \(duration.formatted(.number.precision(.fractionLength(0))))")
let barWidth: CGFloat = max(300, duration * zoom)
ZStack(alignment: Alignment(horizontal: .marker, vertical: .bottom)) {
ZStack(alignment: .leading) {
// The bar representing the duration.
Rectangle().fill(Color.gray)
// The bar representing the elapsed time.
Rectangle().fill(Color.accentColor)
.frame(width: barWidth * (elapsed / duration))
.alignmentGuide(.marker) { d in d[.trailing] }
}
.frame(width: barWidth, height: 20)
Rectangle().fill(Color.primary).frame(width: 3)
.alignmentGuide(.marker) { d in d[HorizontalAlignment.trailing] }
.id(1)
}
.frame(height: 30)
.onChange(of: elapsed, perform: { _ in proxy.scrollTo(1, anchor: .center) })
}
.onChange(of: elapsed, perform: { newValue in if newValue > duration { exit(0) } })
}
}
Slider(value: $zoom, in: 0.5...40, label: { Text("Zoom") })
let s = zoom.formatted(.number.precision(.fractionLength(1)))
Text("\(s)")
}
.padding()
}
}