I am trying to recreate this view from an app. essentially its two repeating/infinite scrollable views. the top contains the days of the week, and the bottom is a paging view that shows the classes on that day. Interacting with either scrollable view (even slightly) will animate both. both views are infinite/repeating, and the items (text) at the top are variable width. i believe the snapping of the top view is dependent on the paging of the bottom, as you cant free scroll it.
GIF demonstrating desired view
i am aware that the 2 views can be synced using the iOS 17 apis for scroll view, but the scroll only syncs after the scroll is completed. as you can see below, the other scrollview doesn't sync until its been scrolled a certain amount.
GIF demonstrating synced position using scrollPosition
import SwiftUI
struct ContentView: View {
@State private var scrollPosition1: Int? = 1
@State private var scrollPosition2: Int? = 1
var body: some View {
GeometryReader { geometry in
VStack {
Text("Current Index: \(scrollPosition1 ?? 0)")
.padding()
// First ScrollView, view aligned
createScrollView(for: $scrollPosition1, color: .blue, frameWidth: geometry.size.width/3)
.scrollTargetBehavior(.viewAligned)
// Second ScrollView, paging
createScrollView(for: $scrollPosition2, color: .green, frameWidth: geometry.size.width)
.scrollTargetBehavior(.paging)
}
// When first one changes, update the second.
.onChange(of: scrollPosition1) { oldValue, newValue in
withAnimation() {
if scrollPosition2 != newValue {
scrollPosition2 = newValue
}
}
}
// When second one changes, update the first.
.onChange(of: scrollPosition2) { oldValue, newValue in
withAnimation() {
if scrollPosition1 != newValue {
scrollPosition1 = newValue
}
}
}
}
}
@ViewBuilder
private func createScrollView(for position: Binding<Int?>, color: Color, frameWidth: CGFloat) -> some View {
ScrollView(.horizontal) {
HStack(spacing: 0) {
ForEach((1...30), id: \.self) { index in
Text("\(index)")
.font(.title2)
.frame(width: frameWidth, height: 70)
.background(color.opacity((index == position.wrappedValue) ? 0.4 : 0.2))
.cornerRadius(30)
.id(index)
}
}
.scrollTargetLayout()
}
.scrollIndicators(.hidden)
// Bind this scroll view to its specific position state
.scrollPosition(id: position, anchor: .leading)
}
}
#Preview {
ContentView()
}
I'm fairly new to swiftUI, but i cant seem to find any way of replicating that view, or examples that contain all the elements. i would really appreciate some help with this. Thank You!
HStack
withDragGesture
and offset instead. For content that cycles (loops), this may be the best approach anyway.