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+

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.