What You Will Build
A five-star rating component where tapping a star fills all stars up to that level with a bounce animation. The selected star briefly scales up with a spring effect, and a feedback label with descriptive text appears below. A submit button slides in once a rating is selected. This is used in review screens, feedback forms, and product rating interfaces.
Why This Pattern Matters
Star ratings are one of the most common interactive components in mobile apps. Building one teaches you how to combine tap gestures with conditional SF Symbol rendering and spring-based scale animations.
Key SwiftUI Concepts
- Conditional SF Symbols:
"star.fill"vs"star"based on rating state. - Spring animation with low dampingFraction for the bounce effect on the selected star.
- Conditional views with .transition() for the feedback text and submit button.
The Complete Rating View
struct RatingDemo: View {
@State private var rating: Int = 0
@State private var hoverRating: Int = 0
var body: some View {
VStack(spacing: 30) {
Text("Rate Your Experience")
.font(.title2.bold())
HStack(spacing: 12) {
ForEach(1...5, id: \.self) { star in
Image(systemName: star <= (hoverRating > 0
? hoverRating : rating)
? "star.fill" : "star")
.font(.system(size: 40))
.foregroundStyle(
star <= (hoverRating > 0
? hoverRating : rating)
? .yellow
: .gray.opacity(0.3))
.scaleEffect(star == rating ? 1.3 : 1.0)
.animation(
.spring(response: 0.3,
dampingFraction: 0.5),
value: rating)
.onTapGesture {
withAnimation { rating = star }
}
}
}
if rating > 0 {
VStack(spacing: 8) {
Text(ratingText)
.font(.headline)
Text(ratingEmoji)
.font(.system(size: 50))
}
.transition(.scale.combined(with: .opacity))
}
if rating > 0 {
Button("Submit Rating") {}
.buttonStyle(.borderedProminent)
.transition(.move(edge: .bottom)
.combined(with: .opacity))
}
}
.animation(.spring, value: rating)
}
var ratingText: String {
switch rating {
case 1: return "Poor"
case 2: return "Fair"
case 3: return "Good"
case 4: return "Great"
case 5: return "Excellent!"
default: return ""
}
}
}
How the Bounce Works
The .scaleEffect(star == rating ? 1.3 : 1.0) enlarges only the most recently tapped star. Combined with .spring(response: 0.3, dampingFraction: 0.5), the star overshoots to 1.3x size and bounces back. The low dampingFraction (0.5) creates a visible bounce; higher values (0.8+) would dampen the effect.
Tips and Pitfalls
- hoverRating is included for iPad pointer support. On touch-only devices, it stays at 0 and the rating state drives the display.
- .spring on the parent VStack ensures that the feedback text and submit button animate smoothly when rating changes.
- Accessibility: Add
.accessibilityLabel("\(star) star")and.accessibilityAddTraits(.isButton)to each star for VoiceOver support. - Half-star ratings: Use a DragGesture across the star row and calculate the position to support 0.5 increments.
iOS Version: iOS 15+