What is the proper way to end a CAEmitterLayer in Swift?

2.1k Views Asked by At

I've mostly seen examples of continuous emitters in Swift, and I've found one example in Obj-C by setting the birthRates of the emitter cells to 0.0, but it doesn't seem to work, so I must be doing something wrong. In my example, I can see the message that the birth rate was set to 0 sixteen times, but the particles continue to flow endlessly.

@IBAction func particleBtnAction(_ sender: Any) {

    let emitter = CAEmitterLayer()
    emitter.emitterPosition = CGPoint(x: self.view.frame.size.width / 2, y: -10)
    emitter.emitterShape = kCAEmitterLayerLine
    emitter.emitterSize = CGSize(width: self.view.frame.size.width, height: 2.0)
    emitter.emitterCells = generateEmitterCells()
    self.view.layer.addSublayer(emitter)

    // perform selector after 1.5 seconds when particles start
    perform(#selector(endParticles), with: emitter, afterDelay: 1.5)

}

private func generateEmitterCells() -> [CAEmitterCell] {

    var cells:[CAEmitterCell] = [CAEmitterCell]()
    for index in 0..<16 {
        let cell = CAEmitterCell()
        cell.birthRate = 4.0
        cell.lifetime = 1.0
        cell.lifetimeRange = 0
        cell.velocity = 0.7
        cell.velocityRange = 0
        cell.emissionLongitude = CGFloat(Double.pi)
        cell.emissionRange = 0.5
        cell.spin = 3.5
        cell.spinRange = 0
        cell.scaleRange = 0.25
        cell.scale = 0.1
        cells.append(cell)
    }
    return cells
}

@objc func endParticles(emitterLayer:CAEmitterLayer) {

    for emitterCell in emitterLayer.emitterCells! {
        emitterCell.birthRate = 0.0
        print("birth rate set to 0")
    }

}
4

There are 4 best solutions below

2
On

Setting the CAEmitterLayer's lifetime to zero stops any new emitterCells being emitted:

@objc func endParticles(emitterLayer:CAEmitterLayer) {
    emitterLayer.lifetime = 0.0
}
0
On

What worked for me is this:

let emmitter = CAEmitterLayer()
let cell = makeCell()
emmitter.emitterCells = [cell]
view.layer.addSublayer(emmitter)

 DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    emmitter.removeFromSuperlayer()
 }
1
On

You can use key paths to assign a name to each cell and loop through them, changing each cell's property when you want to change them:

private func generateEmitterCells() -> [CAEmitterCell] {

    var cells:[CAEmitterCell] = [CAEmitterCell]()
    for index in 0..<16 {
        let cell = CAEmitterCell()
        cell.birthRate = 4.0
        cell.lifetime = 1.0
        cell.lifetimeRange = 0
        cell.velocity = 0.7
        cell.velocityRange = 0
        cell.emissionLongitude = CGFloat(Double.pi)
        cell.emissionRange = 0.5
        cell.spin = 3.5
        cell.spinRange = 0
        cell.scaleRange = 0.25
        cell.scale = 0.1
        cell.name = "cell\(index)" // cell name
        cells.append(cell)
    }
    return cells
}

@objc func endParticles(emitterLayer:CAEmitterLayer) {

    for i in 0..<(emitterLayer.emitterCells?.count ?? 0){

        emitterLayer.setValue(0.0, forKeyPath: "emitterCells.cell\(i).birthRate")
        print("birth rate set to 0")

    }

}
0
On

You might try setting the isHidden property when you want to endParticles

emitter.isHidden = true

But note that all the cells instantly vanish, no matter when they were emitted, or their lifetime.

Another possibility would be to set all the scale related properties to 0, and then the lifetime and birthrate would not matter, as newly emitted cells would not be visible.

cell.scaleSpeed = 0
cell.scaleRange = 0
cell.scale = 0