Search code examples
iosuikitcore-animationcaemitterlayercaemittercell

CAEmitterLayer, eventually, has delay when adding EmitterCells


A very bizarre issue we've been seeing (gifs below),

  1. We have a presented View Controller that has a TeamBadgeView, which is a button that emits emoji as CAEmitterCells
  2. Tapping this button lets users spam a fire emoji on their screen
  3. Dismissing the presented view controller, and re-present the view controller, and now there is a delay. The more times I present/dismiss the view controller, the CAEmitterCell becomes more and more unresponsive
  4. Confirmed that this is not a leak issue, the view controller and button are being properly deallocated
  5. I have tried moving the CAEmitterLayer and CAEmitterCell around, holding a reference in the button, and declaring locally, but similar issues
  6. Perhaps most bizarre, if I do not press the button at all, and simply present/dismiss the viewcontroller many times, and then press the button, there is a delay. The only time there isn't a delay is pressing the button on the first time the View Controller is presented
  7. I have confirmed that the button's action is being fired correct, everytime I spam the button. It's just that the emitter cell is not rendering for a few seconds. And some of the emitter cells just don't render at all

It's gotten to the mind-boggling point, does anybody have any ideas or leads on what this could be?

First presentation of ViewController:
enter image description here

After 5th presentation of ViewController (Pressing button at same rate):

enter image description here

ViewController code:

let teamBadgeView = TeamBadgeView.fromNib()
teamBadgeView.configure()

Button code:

class TeamBadgeView: UIView {
    let emitter = CAEmitterLayer()
    let fireSize = CGSize(width: 16, height: 18)
    let fireScale: CGFloat = 0.8

    func configure() {
        emitter.seed = UInt32(CACurrentMediaTime())
        emitter.emitterPosition = CGPoint(x: bounds.midX, y: 0)
        emitter.emitterShape = CAEmitterLayerEmitterShape.line
        emitter.emitterSize = fireSize
        emitter.renderMode = CAEmitterLayerRenderMode.additive
        layer.addSublayer(emitter)
    }

    @IBAction func tapAction(_ sender: Any) {
        emitFire()
    }

    private func emitFire() {
        let cell = CAEmitterCell()
        let beginTime = CACurrentMediaTime()
        cell.birthRate = 1
        cell.beginTime = beginTime
        cell.duration = 1
        cell.lifetime = 1
        cell.velocity = 250
        cell.velocityRange = 50
        cell.yAcceleration = 100
        cell.alphaSpeed = -1.5
        cell.scale = fireScale
        cell.emissionRange = .pi/8
        cell.contents = NSAttributedString(string: "🔥").toImage(size: fireSize)?.cgImage

        emitter.emitterCells = [cell]
    }
}

Solution

  • Instead of setting the emitterCells array every time:

    emitter.emitterCells = [cell]
    

    ...append the new cell to it. Make sure to initialize it to an empty array if it's nil though, or else the append will not work:

    if emitter.emitterCells == nil {
        emitter.emitterCells = []
    }
    
    emitter.emitterCells?.append(cell)