I am implementing confetti impact with particles. I used for it CAEmitterLayer, and it really works virtually good, however generally particles aren’t emitted. I attempted my finest and debugged it (set colours to emitter layers) and I do know that the layers are added. Here is the complete code:
import UIKit
last class ViewController: UIViewController {
// MARK: - UI
personal lazy var actionButton: UIButton = { [weak self] in
let button = UIButton(sort: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Begin confetti", for: .regular)
button.setTitleColor(.white, for: .regular)
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 16
button.addAction(UIAction { _ in
self?.startConfettiShow()
}, for: .touchUpInside)
return button
}()
personal let confettiContainer: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
tremendous.viewDidLoad()
view.backgroundColor = .grey
view.addSubview(confettiContainer)
view.addSubview(actionButton)
NSLayoutConstraint.activate([
confettiContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
confettiContainer.bottomAnchor.constraint(equalTo: actionButton.topAnchor, constant: -50),
confettiContainer.widthAnchor.constraint(equalToConstant: 300),
confettiContainer.heightAnchor.constraint(equalToConstant: 300),
actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 200),
actionButton.widthAnchor.constraint(equalToConstant: 150),
actionButton.heightAnchor.constraint(equalToConstant: 40),
])
}
// MARK: - Confetti Logic
personal func startConfettiShow() {
// Take away outdated emitters
confettiContainer.layer.sublayers?.removeAll(the place: { $0 is CAEmitterLayer })
view.layoutIfNeeded()
let xOffset: CGFloat = 90
let emissionOffset: CGFloat = 0.1
let left = makeEmitter(xOffset: -xOffset, emissionLongitudeOffset: emissionOffset)
let proper = makeEmitter(xOffset: xOffset, emissionLongitudeOffset: -emissionOffset)
confettiContainer.layer.addSublayer(left)
confettiContainer.layer.addSublayer(proper)
debugEmitter(left, colour: .purple)
debugEmitter(proper, colour: .yellow)
DispatchQueue.foremost.async {
CATransaction.start()
CATransaction.setDisableActions(true)
let now = CACurrentMediaTime()
left.beginTime = now
proper.beginTime = now
left.birthRate = 1
proper.birthRate = 1
CATransaction.commit()
}
// Cease emission
DispatchQueue.foremost.asyncAfter(deadline: .now() + 1.0) {
left.birthRate = 0
proper.birthRate = 0
}
// Take away the layers
DispatchQueue.foremost.asyncAfter(deadline: .now() + 5.0) {
left.removeFromSuperlayer()
proper.removeFromSuperlayer()
}
}
personal func makeEmitter(xOffset: CGFloat,
emissionLongitudeOffset: CGFloat) -> CAEmitterLayer {
let emitter = CAEmitterLayer()
emitter.body = confettiContainer.bounds
emitter.emitterShape = .level
emitter.emitterPosition = CGPoint(
x: confettiContainer.bounds.midX + xOffset,
y: confettiContainer.bounds.maxY - 10
)
emitter.birthRate = 0
let cells: [CAEmitterCell] = ViewController.colours.flatMap { colour in
ViewController.shapes.map { form in
let cell = CAEmitterCell()
cell.birthRate = 15
cell.lifetime = 3.5
cell.velocity = .random(in: 250...400)
cell.velocityRange = 80
cell.emissionLongitude = -.pi / 2 + emissionLongitudeOffset
cell.emissionRange = .pi / 8
cell.yAcceleration = 200
cell.spin = .random(in: -3...3)
cell.spinRange = 4
cell.scale = .random(in: 0.08...0.14)
cell.alphaSpeed = -0.4
cell.colour = colour.cgColor
cell.contents = form
return cell
}
}
emitter.emitterCells = cells
return emitter
}
personal func debugEmitter(_ emitter: CAEmitterLayer, colour: UIColor) {
emitter.borderColor = colour.cgColor
emitter.borderWidth = 1
emitter.backgroundColor = colour.withAlphaComponent(0.15).cgColor
print("Emitter body:", emitter.body)
print("Emitter place:", emitter.emitterPosition)
}
}
// MARK: - Static sources
personal extension ViewController {
static let colours: [UIColor] = [
.systemPink, .systemYellow, .systemBlue, .systemGreen, .systemPurple
]
static let shapes: [CGImage] = [
CGImage.cgImageCircle(diameter: 36),
CGImage.cgImageSquare(side: 32),
CGImage.cgImageTriangle(size: 36),
].compactMap { $0 }
}
// MARK: - Form mills
extension CGImage {
static func cgImageCircle(diameter: CGFloat) -> CGImage? {
let measurement = CGSize(width: diameter, top: diameter)
return UIGraphicsImageRenderer(measurement: measurement).picture { ctx in
UIColor.white.setFill()
ctx.cgContext.fillEllipse(in: CGRect(origin: .zero, measurement: measurement))
}.cgImage
}
static func cgImageSquare(facet: CGFloat) -> CGImage? {
let measurement = CGSize(width: facet, top: facet)
return UIGraphicsImageRenderer(measurement: measurement).picture { ctx in
UIColor.white.setFill()
ctx.cgContext.fill(CGRect(origin: .zero, measurement: measurement))
}.cgImage
}
static func cgImageTriangle(measurement: CGFloat) -> CGImage? {
let s = CGSize(width: measurement, top: measurement)
return UIGraphicsImageRenderer(measurement: s).picture { ctx in
UIColor.white.setFill()
let path = UIBezierPath()
path.transfer(to: CGPoint(x: s.width / 2, y: 0))
path.addLine(to: CGPoint(x: s.width, y: s.top))
path.addLine(to: CGPoint(x: 0, y: s.top))
path.shut()
path.fill()
}.cgImage
}
}
I am setting beginTime
and birthRate
after emitters being added to view’s layer, I even used CATransaction.start()
and CATransaction.commit()
(ChatGPT recommendation), nevertheless it did not labored. With all these enhancements it began working higher, I see much less of emitters not working, however nonetheless generally it would not work, so the ingredient is unstable.
Verify the screenshot. There’re two emitters, however just one is emitting. Not often they do not emit each.