CAEmitterLayer, eventually, has delay when adding EmitterCells

166 views Asked by At

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]
    }
}
2

There are 2 answers

0
TylerP On BEST ANSWER

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)
0
A O On

Thanks to @TylerTheCompiler we were able to figure this out, and it was really lame.

One line change, instead of setting the emitterCells, we needed to append

    emitter.emitterCells = [cell]

became

    emitter.emitterCells?.append(cell)

Why we didn't notice this was because it appears there is a weird interaction with Hero transitions. Our ViewController is presented via a Hero Transition, and for some reason the first time it's presented, emitterCells = [cell] works as expected... but then for some reason, for each subsequent Hero Transition to the ViewController, the cells start emitting slower and slower until it's back to the expected slow state. Incredibly strange, perhaps a bug in Hero, but who knows