What You Will Build

A horizontal strip of colored cards that scrolls continuously from right to left without user interaction, looping seamlessly when it reaches the end. This is the auto-scrolling carousel pattern used for sponsor logos, feature highlights, and category browsing in shopping apps.

Why This Pattern Matters

Auto-scrolling carousels draw attention to content that might otherwise be missed. The infinite loop illusion is achieved by tripling the item array and resetting the offset when one set scrolls off screen. No complex gesture handling is needed since the animation runs on its own.

Key SwiftUI Concepts

  • Array tripling creates enough duplicates for seamless looping.
  • .linear animation with repeatForever produces constant-speed scrolling.
  • GeometryReader measures the available width for layout calculations.

The Complete Implementation

struct InfiniteScrollDemo: View {
    let items = ["Design", "Code", "Build", "Test", "Ship", "Scale"]
    let colors: [Color] = [.red, .blue, .green, .orange, .purple, .cyan]
    @State private var offset: CGFloat = 0

    var body: some View {
        VStack(spacing: 30) {
            Text("Infinite Scroll")
                .font(.title2.bold())

            GeometryReader { geo in
                let width = geo.size.width
                HStack(spacing: 12) {
                    ForEach(0..<items.count * 3, id: \.self) { index in
                        let i = index % items.count
                        RoundedRectangle(cornerRadius: 12)
                            .fill(colors[i].gradient)
                            .frame(width: 100, height: 100)
                            .overlay {
                                Text(items[i])
                                    .font(.caption.bold())
                                    .foregroundStyle(.white)
                            }
                    }
                }
                .offset(x: offset)
                .onAppear {
                    let totalWidth = CGFloat(items.count) * 112
                    withAnimation(.linear(duration: 8)
                                  .repeatForever(autoreverses: false)) {
                        offset = -totalWidth
                    }
                }
            }
            .frame(height: 100)
            .clipped()

            Text("Auto-scrolling cards")
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    }
}

How the Loop Works

The items array is rendered three times (count * 3), creating three identical sets of cards side by side. The animation moves the offset left by exactly one set width. When the first set scrolls completely off screen, the second set is in the same position the first started in, making the loop appear seamless. The .clipped() modifier hides the overflow.

Tips and Pitfalls

  • Calculate totalWidth accurately: Each card is 100pt wide plus 12pt spacing = 112pt per item. If these numbers do not match your HStack spacing, you get a visible jump at the loop point.
  • Use .clipped(): Without it, cards render outside the visible area and overlap other content.
  • Linear animation only: Eased animations accelerate and decelerate, creating an unnatural pause at the loop seam. Always use .linear for constant-speed scrolling.
  • Pause on interaction: To let users tap cards, stop the animation on drag gesture and resume on release.
  • Performance: For large item counts, use LazyHStack. But for small sets (under 20 visible), a regular HStack performs fine.

iOS Version: iOS 15+

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.