
Here’s a SwiftUI implementation of a card carousel using HStack
, ScrollView
, and GeometryReader
to arrange cards horizontally:
Explanation:
- Card Struct: A simple data model to hold information for each card (image, title, description).
- CardCarouselView:
cards
array stores the card data.ScrollView(.horizontal)
creates a horizontal scrolling view.HStack
arranges the card views horizontally with spacing.ForEach
iterates over thecards
array to createCardView
for each card.GeometryReader
is used to get the position of each card in the scroll view, allowing for the 3D rotation effect.
- CardView:
- Represents a single card in the carousel.
VStack
arranges the image, title, and description vertically.Image
displays the card image (replace with actual image names).Text
views display the title and description.
- 3D Rotation Effect:
rotation3DEffect
is used to apply a subtle 3D rotation to each card as it moves through the carousel. The angle of rotation is calculated based on the card’s position.
- Styling:
- Padding is added around the carousel and the content of each card.
- A white background, rounded corners, and a shadow are applied to each card for visual appeal.
import SwiftUI
import SwiftUI
struct Card: Identifiable {
let id = UUID()
let imageName: String
let title: String
let description: String
}
struct CardCarouselView: View {
let cards: [Card] = [
Card(imageName: "card1", title: "Card1", description: "Description for card 1"),
Card(imageName: "card2", title: "Card2", description: "Description for card 2"),
Card(imageName: "card3", title: "Card3", description: "Description for card 3"),
Card(imageName: "card4", title: "Card4", description: "Description for card 4"),
]
@State private var selectedIndex = 0 // Track the currently selected card
var body: some View {
ZStack {
Color(hex: 0xF5F5F5) // Light gray background color
.edgesIgnoringSafeArea(.all)
VStack {
Text("Featured Products") // Add title
.font(.title)
.fontWeight(.bold)
.padding(.top, 20)
Text("Explore our latest and greatest products.") // Add description
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.bottom, 10)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(cards.indices, id: \.self) { index in
GeometryReader { geometry in
CardView(card: cards[index])
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20), axis: (x: 0, y: 1, z: 0))
.onTapGesture {
withAnimation {
selectedIndex = index
}
}
}
.frame(width: 300, height: 250)
}
}
.padding(.horizontal, 30)
// .padding(.bottom, 30)
}
.background(Color(hex: 0xF5F5F5)) // Light gray background (F5F5F5)
HStack { // Page indicator dots
ForEach(0..<cards.count, id: \.self) { index in
Circle()
.fill(index == selectedIndex ? Color.blue : Color.gray)
.frame(width: 8, height: 8)
}
}
.padding(.bottom, 20)
}
}
}
}
struct CardView: View {
let card: Card
var body: some View {
VStack(alignment: .leading) {
Image(card.imageName)
.resizable()
.scaledToFill()
.frame(height: 100)
.clipped()
Text(card.title)
.font(.headline)
.padding(.top, 5)
Text(card.description)
.font(.caption)
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 3)
}
}
#Preview {
CardCarouselView()
}
extension Color {
init(hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 8) & 0xff) / 255,
blue: Double(hex & 0xff) / 255,
opacity: alpha
)
}
}
import SwiftUI
import SwiftUI
struct Card: Identifiable {
let id = UUID()
let imageName: String
let title: String
let description: String
}
struct CardCarouselView: View {
let cards: [Card] = [
Card(imageName: "card1", title: "Card1", description: "Description for card 1"),
Card(imageName: "card2", title: "Card2", description: "Description for card 2"),
Card(imageName: "card3", title: "Card3", description: "Description for card 3"),
Card(imageName: "card4", title: "Card4", description: "Description for card 4"),
]
@State private var selectedIndex = 0 // Track the currently selected card
var body: some View {
ZStack {
Color(hex: 0xF5F5F5) // Light gray background color
.edgesIgnoringSafeArea(.all)
VStack {
Text("Featured Products") // Add title
.font(.title)
.fontWeight(.bold)
.padding(.top, 20)
Text("Explore our latest and greatest products.") // Add description
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.bottom, 10)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(cards.indices, id: \.self) { index in
GeometryReader { geometry in
CardView(card: cards[index])
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20), axis: (x: 0, y: 1, z: 0))
.onTapGesture {
withAnimation {
selectedIndex = index
}
}
}
.frame(width: 300, height: 250)
}
}
.padding(.horizontal, 30)
// .padding(.bottom, 30)
}
.background(Color(hex: 0xF5F5F5)) // Light gray background (F5F5F5)
HStack { // Page indicator dots
ForEach(0..<cards.count, id: \.self) { index in
Circle()
.fill(index == selectedIndex ? Color.blue : Color.gray)
.frame(width: 8, height: 8)
}
}
.padding(.bottom, 20)
}
}
}
}
struct CardView: View {
let card: Card
var body: some View {
VStack(alignment: .leading) {
Image(card.imageName)
.resizable()
.scaledToFill()
.frame(height: 100)
.clipped()
Text(card.title)
.font(.headline)
.padding(.top, 5)
Text(card.description)
.font(.caption)
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 3)
}
}
#Preview {
CardCarouselView()
}
extension Color {
init(hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 8) & 0xff) / 255,
blue: Double(hex & 0xff) / 255,
opacity: alpha
)
}
}
Key Improvements:
- Horizontal Scrolling: The
ScrollView
with.horizontal
allows users to swipe horizontally to browse through the cards. - Card Layout:
HStack
arranges the cards side-by-side. - Dynamic Cards:
ForEach
dynamically creates card views based on thecards
data. - Visual Appeal: Each card has a visually appealing layout with an image, title, description, rounded corners, and a shadow.
- 3D Rotation Effect: Adds a touch of interactivity and depth to the carousel.
How to Use:
- Replace the sample
cards
data with your actual card content (images, titles, descriptions). - Customize the appearance of the cards and the carousel to match your design preferences.