Create an auto-scroll 3D carousel in SwiftUI
In this example, we have a ContentView
that displays a stack of CarouselCardView
instances using a ZStack
. The CarouselCardView
represents an individual card in the carousel and contains an image and a title.
import SwiftUI
struct ContentView: View {
@State private var currentIndex: Int = 0
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
let carouselItems = [
CarouselItem(title: "Gorilla", image: "cover-gorilla"),
CarouselItem(title: "Cheetah", image: "cover-cheetah"),
CarouselItem(title: "Buffalo", image: "cover-buffalo"),
CarouselItem(title: "Elephant", image: "cover-elephant"),
CarouselItem(title: "Giraffe", image: "cover-giraffe"),
// Add more items as needed
]
var body: some View {
VStack {
ZStack {
ForEach(carouselItems.indices) { index in
CarouselCardView(item: carouselItems[index], currentIndex: $currentIndex, cardIndex: index)
.rotation3DEffect(.degrees(getAngle(index: index)), axis: (x: 0, y: 1, z: 0))
.offset(x: getOffsetX(index: index))
.animation(.default)
}
}
.gesture(DragGesture()
.onChanged({ value in
timer.upstream.connect().cancel() // Cancel auto-scrolling while dragging
let offset = value.translation.width
let cardWidth = UIScreen.main.bounds.width * 0.8
let normalizedOffset = Double(offset / cardWidth)
let newIndex = currentIndex - Int(round(normalizedOffset))
currentIndex = min(max(newIndex, 0), carouselItems.count - 1)
})
)
}
.onReceive(timer) { _ in
currentIndex = (currentIndex + 1) % carouselItems.count // Auto-scroll to the next card
}
}
func getAngle(index: Int) -> Double {
let angleOffset = -Double(currentIndex) * 30
let cardIndex = Double(index)
return cardIndex * 30 + angleOffset
}
func getOffsetX(index: Int) -> CGFloat {
let cardIndex = Double(index)
let offset = cardIndex - Double(currentIndex)
let cardWidth = UIScreen.main.bounds.width * 0.8
return CGFloat(offset * cardWidth)
}
}
struct CarouselCardView: View {
let item: CarouselItem
@Binding var currentIndex: Int
let cardIndex: Int
var body: some View {
VStack {
Image(item.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: UIScreen.main.bounds.width * 0.8, height: UIScreen.main.bounds.height * 0.3)
.cornerRadius(10)
Text(item.title)
.font(.title)
.fontWeight(.bold)
.padding(.top, 10)
}
.frame(width: UIScreen.main.bounds.width * 0.7, height: UIScreen.main.bounds.height * 0.4)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
.onTapGesture {
currentIndex = cardIndex
}
}
}
struct CarouselItem {
let title: String
let image: String
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The carousel supports swiping left or right to navigate between cards. It uses a DragGesture
to track the drag offset and updates the currentIndex
accordingly. The cards are rotated and offset in 3D space based on the currentIndex
, giving the illusion of a 3D carousel.
You can customize the carouselItems
array to add more items to the carousel. Make sure to replace the image names in the array with the actual image.
Cheers!
Happy Coding!