SwiftUI Tutorials: Designing a Dynamic Confetti Effect

JC
3 min readMay 8, 2024
Confetti animation shown over an application’s home screen.

Adding visual effects to an app can significantly enhance user interaction and satisfaction. In this tutorial, I’ll guide you through creating a delightful confetti effect in SwiftUI, which can be used to celebrate achievements, milestones, or any festive moments in your app.

Understanding the Components

We will build this effect using three main components:

  1. ConfettiView: Represents a single confetti piece.
  2. ConfettiContainerView: Manages multiple confetti pieces.
  3. DisplayConfettiModifier: A view modifier to control the display of the confetti.

ConfettiView

The ConfettiView struct defines the appearance and animation of a single confetti piece. It uses random colors and rotates in 3D space, creating a dynamic visual.

struct ConfettiView: View {
@State var animate = false
@State var xSpeed = Double.random(in: 0.7...2)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()

var body: some View {
Rectangle()
.fill([Color.orange, Color.green, Color.blue, Color.red, Color.yellow].randomElement() ?? Color.green)
.frame(width: 20, height: 20)
.onAppear(perform: { animate = true })
.rotation3DEffect(.degrees(animate ? 360 : 0), axis: (x: 1, y: 0, z: 0))
.animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false), value: animate)
.rotation3DEffect(.degrees(animate ? 360 : 0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
.animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false), value: animate)
}
}

ConfettiContainerView

This view layers multiple ConfettiView components into a ZStack and positions them randomly across the screen.

struct ConfettiContainerView: View {
var count: Int = 50
@State var yPosition: CGFloat = 0

var body: some View {
ZStack {
ForEach(0..<count, id: \.self) { _ in
ConfettiView()
.position(
x: CGFloat.random(in: 0...UIScreen.main.bounds.width),
y: yPosition != 0 ? CGFloat.random(in: 0...UIScreen.main.bounds.height) : yPosition
)
}
}
.ignoresSafeArea()
.onAppear {
yPosition = CGFloat.random(in: 0...UIScreen.main.bounds.height)
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
}

DisplayConfettiModifier

A ViewModifier that wraps any view and overlays the confetti effect when triggered. It includes optional haptic feedback for supported devices.

struct DisplayConfettiModifier: ViewModifier {
@Binding var isActive: Bool {
didSet {
if !isActive {
opacity = 1
}
}
}
@State private var opacity = 1.0 {
didSet {
if opacity == 0 {
isActive = false
}
}
}

private let animationTime = 3.0
private let fadeTime = 2.0

func body(content: Content) -> some View {
if #available(iOS 17.0, *) {
content
.overlay(isActive ? ConfettiContainerView().opacity(opacity) : nil)
.sensoryFeedback(.success, trigger: isActive)
.task {
await handleAnimationSequence()
}
} else {
content
.overlay(isActive ? ConfettiContainerView().opacity(opacity) : nil)
.task {
await handleAnimationSequence()
}
}
}

private func handleAnimationSequence() async {
do {
try await Task.sleep(nanoseconds: UInt64(animationTime * 1_000_000_000))
withAnimation(.easeOut(duration: fadeTime)) {
opacity = 0
}
} catch {}
}
}

extension View {
func displayConfetti(isActive: Binding<Bool>) -> some View {
self.modifier(DisplayConfettiModifier(isActive: isActive))
}
}

Putting It All Together

You can integrate the confetti effect into any part of your app. Here’s how you might use it within a view:

import SwiftUI

struct CelebrationView: View {
@State private var showConfetti = false

var body: some View {
VStack {
Button("Celebrate") {
showConfetti = true
}
}
.displayConfetti(isActive: $showConfetti)
}
}

Feel free to adapt and expand this effect to fit the theme and functionality of your app. Celebrate your users’ achievements with a burst of colorful confetti!

--

--

JC

Software engineer & founder. Built ML investing tools, Wall Bounce game, BetTheFarm, Barrier app, & Annot8. Innovating solutions daily.