1

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]
    }
}
A O
  • 5,516
  • 3
  • 33
  • 68
  • 1
    Could you post a demo project? I'd love to try this out. – matt Dec 12 '19 at 20:18
  • matt! great to see you, it's been years since I've had you on one of my threads :) let me create a project right now – A O Dec 12 '19 at 20:27
  • Uploaded here: https://github.com/MattyAyOh/FireDemo this project actually revealed something: the fire actually *should* be slow! It looks like it should have never really worked in the first place. In our ViewController, we use a Hero Transition (https://github.com/HeroTransitions/Hero), and it appears this transition is what somehow makes the fire work as expected. When removing the transition it went back to being slow... – A O Dec 12 '19 at 20:39
  • Well it sounds like you don’t need me then! – matt Dec 12 '19 at 20:45
  • Hi, so I made my own project with the code in your post (not your demo project) and I noticed that in order for many fire images to show I had to append the new cell to `emitterCells` instead of setting it to `[cell]` (and if `emitterCells` is nil then I had to initialize it to an empty array first). So maybe that's the reason it's going "slow"? – TylerP Dec 12 '19 at 20:46
  • nono, I do! I do! – A O Dec 12 '19 at 20:46
  • @matt, we still have the issue, because we cannot remove Hero transitions. I'm experimenting a lot right now though so I can just keep you posted if you're curious. TylerTheCompiler, that sounds interesting, let me try that as well in our production code – A O Dec 12 '19 at 20:47
  • Yeah I was able to push/pop a view controller with this emitter and it never slowed down or had a delay once I made that one change. – TylerP Dec 12 '19 at 20:48
  • Oh okay great! I'll add it as an answer then! Yeah these Hero transitions must be causing some extra weirdness. – TylerP Dec 12 '19 at 20:52

2 Answers2

1

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)
TylerP
  • 9,600
  • 4
  • 39
  • 43
0

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

A O
  • 5,516
  • 3
  • 33
  • 68