Created
March 9, 2022 14:53
-
-
Save kierancrown/bf83669a7f301bd80cde136fd53bef6a to your computer and use it in GitHub Desktop.
Confetti Animation in SwiftUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftUI | |
enum ConfettiShape { | |
case rectangle | |
case circle | |
} | |
enum ConfettiPosition { | |
case foreground | |
case background | |
} | |
class ConfettiType { | |
let color: UIColor | |
let shape: ConfettiShape | |
let position: ConfettiPosition | |
init(color: UIColor, shape: ConfettiShape, position: ConfettiPosition) { | |
self.color = color | |
self.shape = shape | |
self.position = position | |
} | |
lazy var name = UUID().uuidString | |
lazy var image: UIImage = { | |
let imageRect: CGRect = { | |
switch shape { | |
case .rectangle: | |
return CGRect(x: 0, y: 0, width: 20, height: 13) | |
case .circle: | |
return CGRect(x: 0, y: 0, width: 10, height: 10) | |
} | |
}() | |
UIGraphicsBeginImageContext(imageRect.size) | |
let context = UIGraphicsGetCurrentContext()! | |
context.setFillColor(color.cgColor) | |
switch shape { | |
case .rectangle: | |
context.fill(imageRect) | |
case .circle: | |
context.fillEllipse(in: imageRect) | |
} | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image! | |
}() | |
} | |
var confettiTypes: [ConfettiType] = { | |
let confettiColors = [ | |
(r:149,g:58,b:255), (r:255,g:195,b:41), (r:255,g:101,b:26), | |
(r:123,g:92,b:255), (r:76,g:126,b:255), (r:71,g:192,b:255), | |
(r:255,g:47,b:39), (r:255,g:91,b:134), (r:233,g:122,b:208) | |
].map { UIColor(red: $0.r / 255.0, green: $0.g / 255.0, blue: $0.b / 255.0, alpha: 1) } | |
// For each position x shape x color, construct an image | |
return [ConfettiPosition.foreground, ConfettiPosition.background].flatMap { position in | |
return [ConfettiShape.rectangle, ConfettiShape.circle].flatMap { shape in | |
return confettiColors.map { color in | |
return ConfettiType(color: color, shape: shape, position: position) | |
} | |
} | |
} | |
}() | |
func createConfettiCells() -> [CAEmitterCell] { | |
return confettiTypes.map { confettiType in | |
let cell = CAEmitterCell() | |
cell.name = confettiType.name | |
cell.beginTime = 0.1 | |
cell.birthRate = 100 | |
cell.contents = confettiType.image.cgImage | |
cell.emissionRange = CGFloat(Double.pi) | |
cell.lifetime = 10 | |
cell.spin = 4 | |
cell.spinRange = 8 | |
cell.velocityRange = 0 | |
cell.yAcceleration = 0 | |
// Step 3: A _New_ Spin On Things | |
cell.setValue("plane", forKey: "particleType") | |
cell.setValue(Double.pi, forKey: "orientationRange") | |
cell.setValue(Double.pi / 2, forKey: "orientationLongitude") | |
cell.setValue(Double.pi / 2, forKey: "orientationLatitude") | |
return cell | |
} | |
} | |
struct ConfettiView: UIViewRepresentable { | |
let color: UIColor | |
let shape: ConfettiShape | |
let position: ConfettiPosition | |
let bounds: CGRect | |
func makeUIView(context: Context) -> some UIView { | |
let view = UIView() | |
/* | |
This is the layer that contains the confetti emitter | |
*/ | |
lazy var confettiLayer: CAEmitterLayer = { | |
let emitterLayer = CAEmitterLayer() | |
emitterLayer.emitterCells = confettiCells | |
emitterLayer.emitterPosition = CGPoint(x: bounds.midX, y: bounds.minY - 500) | |
emitterLayer.emitterSize = CGSize(width: bounds.size.width, height: 500) | |
emitterLayer.emitterShape = .rectangle | |
emitterLayer.frame = bounds //view.bounds | |
emitterLayer.beginTime = CACurrentMediaTime() | |
return emitterLayer | |
}() | |
/* | |
This is the actual singular confetti emitter cell | |
*/ | |
lazy var confettiCells: [CAEmitterCell] = { | |
return confettiTypes.map { confettiType in | |
let cell = CAEmitterCell() | |
cell.setValue("plane", forKey: "particleType") | |
cell.setValue(Double.pi, forKey: "orientationRange") | |
cell.setValue(Double.pi / 2, forKey: "orientationLongitude") | |
cell.setValue(Double.pi / 2, forKey: "orientationLatitude") | |
cell.beginTime = 0.1 | |
cell.birthRate = 4 | |
cell.contents = confettiType.image.cgImage | |
cell.emissionRange = CGFloat(Double.pi) | |
cell.lifetime = 10 | |
cell.spin = 4 | |
cell.spinRange = 5 | |
cell.velocityRange = 100 | |
cell.yAcceleration = 88 | |
return cell | |
} | |
}() | |
view.layer.addSublayer(confettiLayer) | |
view.bounds = bounds | |
view.isUserInteractionEnabled = false | |
return view | |
} | |
func updateUIView(_ uiView: UIViewType, context: Context) { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment