HomeiOS DevelopmentCAEmitterLayer randomly would not emit particles

CAEmitterLayer randomly would not emit particles


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.

CAEmitterLayer randomly would not emit particles

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments