Skip to content

Instantly share code, notes, and snippets.

@irace
Created October 10, 2025 16:48
Show Gist options
  • Save irace/ab4bb8ded83fb13012f7c346ec996ac0 to your computer and use it in GitHub Desktop.
Save irace/ab4bb8ded83fb13012f7c346ec996ac0 to your computer and use it in GitHub Desktop.
//
// RainingParticleView.swift
// Provider
//
// Created by Bryan Irace on 1/26/17.
//
//
import ServiceKit
import UIKit
import QuartzCore
struct ParticleDescriptor {
let image: UIImage
let color: UIColor?
}
public enum ParticleType {
case confetti
case money
func descriptors() -> [ParticleDescriptor] {
switch self {
case .confetti:
return [
.confettiPurple,
.confettiRed,
.confettiBlue,
.confettiOrange,
].map({ colorName in
ParticleDescriptor(image: UIImage(asset: .confetti), color: UIColor(named: colorName))
})
case .money:
return [ParticleDescriptor(image: UIImage(asset: .cash), color: nil)]
}
}
}
/// A view that makes it look like money is raining down from the sky.
final class RainingParticleView: UIView {
private enum Constants {
static let emitterHeight: CGFloat = 1
static let birthRate: Float = 120
static let lifetime: Float = 10.5
static let velocity: CGFloat = 262.5
static let velocityRange: CGFloat = 60
static let emissionLongitude = CGFloat(Double.pi)
static let emissionRange = CGFloat(Double.pi/4)
static let spin: CGFloat = 2.625
static let spinRange: CGFloat = 3
static let yAcceleration: CGFloat = 200
}
// MARK: - Mutable state
private var rain = PerformOnce()
// MARK: - Sublayers
private let emitter: CAEmitterLayer
init(frame: CGRect, type: ParticleType) {
emitter = CAEmitterLayer().then {
$0.beginTime = CACurrentMediaTime()
$0.emitterPosition = CGPoint(x: frame.midX, y: 0)
$0.emitterSize = CGSize(width: frame.size.width, height: Constants.emitterHeight)
$0.emitterShape = kCAEmitterLayerLine
$0.emitterCells = RainingParticleView.cells(from: type.descriptors())
}
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Private
private static func cells(from particleDescriptors: [ParticleDescriptor]) -> [CAEmitterCell] {
return particleDescriptors.map({ descriptor in
CAEmitterCell().then {
$0.contents = descriptor.image.cgImage
$0.birthRate = Constants.birthRate
$0.lifetime = Constants.lifetime
$0.velocity = Constants.velocity
$0.velocityRange = Constants.velocityRange
$0.emissionLongitude = Constants.emissionLongitude
$0.emissionRange = Constants.emissionRange
$0.spin = Constants.spin
$0.spinRange = Constants.spinRange
$0.yAcceleration = Constants.yAcceleration
if let color = descriptor.color {
$0.color = color.cgColor
}
}
})
}
// MARK: - Public
/// Start raining down money. Won’t do anything if you call it more than once.
func start() {
rain.performOnce({
layer.addSublayer(emitter)
})
}
/// Stop producing new money.
func stop() {
emitter.birthRate = 0
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment