I’m trying to make a horizontal scrollview that loops. All the solutions I’ve found online for this problem include adding copies of items at the beginning or end of the list. The element(s) inside have state and their appearance changes based on that state.
The problem is that when reaching the end, the next (first) item is stuck with its initial state rather than the current state. This results in jerky behavior: screen recording
I’ve tried this with every example I’ve found online with very similar results each time. The example in the video is made with the following code, although you can find the working bare-bones example here: Github
struct InfiniteHorizontalScrollView2<Content: View, Item: RandomAccessCollection>: View where Item.Element: Identifiable {
var width: CGFloat
var items: Item
var repeatingCount = 1
@ViewBuilder var content: (Item.Element) -> Content
var body: some View {
ScrollView(.horizontal) {
LazyHStack(spacing: 0) {
ForEach(items) { item in
content(item)
}
ForEach(0..<repeatingCount, id: \.self) { index in
let item = Array(items)[index % items.count]
content(item)
}
}
.background {
ScrollViewHelper(
width: width,
itemsCount: items.count
)
}
}
}
}
struct ScrollViewHelper: UIViewRepresentable {
var width: CGFloat
var itemsCount: Int
func makeCoordinator() -> Coordinator {
return Coordinator(
width: width,
itemsCount: itemsCount
)
}
func makeUIView(context: Context) -> some UIView {
return .init()
}
func updateUIView(_ uiView: UIViewType, context: Context) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.06) {
if let scrollView = uiView.superview?.superview?.superview as? UIScrollView, !context.coordinator.isAdded {
scrollView.delegate = context.coordinator
context.coordinator.isAdded = true
}
}
}
class Coordinator: NSObject, UIScrollViewDelegate {
var width: CGFloat
var itemsCount: Int
init(
width: CGFloat,
itemsCount: Int
) {
self.width = width
self.itemsCount = itemsCount
}
var isAdded: Bool = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let minX = scrollView.contentOffset.x
let mainContentSize = CGFloat(itemsCount) * width
if minX > mainContentSize {
scrollView.contentOffset.x -= mainContentSize
}
if minX < 0 {
scrollView.contentOffset.x += mainContentSize
}
}
}
}




