Keep the video info carousel aligned when its width changes

Unfolding an iPhone Duo kept the old scroll offset, leaving the selected
card misaligned. Re-snap to the current card on width change, and clear
the requested position once it settles so the lazy stack doesn't pull
the next swipe back halfway between cards.
This commit is contained in:
Arkadiusz Fal
2026-10-08 23:59:53 +02:00
parent 82236967b7
commit 4d690d6b71

View File

@@ -37,6 +37,7 @@ struct VideoInfoView: View {
// Carousel scroll state (iOS only) - separate from currentVideoIndex to avoid mid-scroll updates
#if os(iOS)
@State private var carouselScrollPosition: Int?
@State private var clearsCarouselPositionWhenIdle = false
#endif
// Continuation loading state
@@ -1011,6 +1012,15 @@ struct VideoInfoView: View {
.scrollTargetBehavior(.viewAligned(limitBehavior: .alwaysByOne))
.scrollPosition(id: $carouselScrollPosition)
.onScrollPhaseChange { oldPhase, newPhase in
if newPhase == .interacting {
clearsCarouselPositionWhenIdle = false
}
// Drop the re-snap request after a width change (see onChange(of: cardWidth))
if newPhase == .idle, clearsCarouselPositionWhenIdle {
clearsCarouselPositionWhenIdle = false
carouselScrollPosition = nil
return
}
// Only update currentVideoIndex when scrolling ends (user lifts finger)
if newPhase == .idle, let newIndex = carouselScrollPosition, newIndex != currentVideoIndex {
currentVideoIndex = newIndex
@@ -1023,6 +1033,22 @@ struct VideoInfoView: View {
proxy.scrollTo(newValue, anchor: .center)
}
}
.onChange(of: cardWidth) { _, _ in
// A width change (iPhone Duo unfold, rotation, split resize) keeps
// the old point offset, leaving the cards misaligned. Re-snap to the
// current card (setting the same id is a no-op, hence the nil hop).
// The LazyHStack keeps re-applying a programmatic position while it
// realizes cards, which stopped the next swipe halfway between two
// cards, so the position is cleared again once the re-snap settles.
guard let index = currentVideoIndex else { return }
carouselScrollPosition = nil
DispatchQueue.main.async {
clearsCarouselPositionWhenIdle = true
withAnimation(.linear(duration: 0.15)) {
carouselScrollPosition = index
}
}
}
.onAppear {
// Initialize scroll position
if carouselScrollPosition == nil, let index = currentVideoIndex {