Search code examples
ioscore-animationquartz

CAEmitterLayer multiple cells interleaved


I have a CAEmitterLayer with an array of CAEmitterCells. I want each the output of all cells to be intermingled. Right now the first cell in the array always draws above the other cells.

I've adjusted the renderMode of the layer and the zAcceleration of each cell but neither has solved this problem.


Some sample code:

func layoutSubviews() {
    // ...
    emitterLayer.frame = self.bounds
    emitterLayer.seed = UInt32(Date().timeIntervalSinceNow)
    emitterLayer.renderMode = kCAEmitterLayerOldestFirst
    emitterLayer.emitterPosition = CGPoint(x: bounds.midX, y: bounds.midY)
    emitterLayer.emitterSize = CGSize.init(width: bounds.width, height: 0)
    emitterLayer.emitterShape = kCAEmitterLayerLine
    self.layer.addSublayer(emitterLayer)
}

private func animate() {
    // ...
    if emitterLayer.emitterCells == nil || emitterLayer.emitterCells?.count == 0 {
        let cells = (1...8).map { i -> CAEmitterCell in
            if let img = UIImage.init(named: "Slice-\(i)") {
                return createEmitterCell(image: img, index: i)
            } else {
                fatalError()
            }
        }
        emitterLayer.emitterCells = cells
    }
}

private func createEmitterCell(image: UIImage, index: Int) -> CAEmitterCell {
    let cell = CAEmitterCell.init()
    cell.isEnabled = true
    cell.contents = image.cgImage

    cell.contentsRect = CGRect(origin: CGPoint.zero, size: CGSize(width: 1, height: 1))

    cell.birthRate = Float(index)
    cell.lifetime = 3
    cell.velocity = 7
    cell.velocityRange = 3
    cell.scale = 0.5

    cell.emissionRange = (95 * CGFloat.pi / 180.0)
    cell.emissionLatitude = (27 * CGFloat.pi / 180.0)
    cell.emissionLongitude = (139 * CGFloat.pi / 180.0)

    cell.xAcceleration = 10.0
    cell.yAcceleration = 100.0
    cell.zAcceleration = 10 * CGFloat(index)

    return cell
}


Solution

  • I have 12 emitter cells and my solution is to randomize the order of emitters. This way each run of the application produces a different ordering.

            emojiCells = "😃💪🏆🎊🎉💯✨✅🔷🙌💫🏁"
                .characters
                .sorted(by: { (a, b) -> Bool in
                    return arc4random() % 2 == 0
                }).enumerated().map({ (offset: Int, element: Character) -> CAEmitterCell in
                    // create emitter cells for each emoji
                    let raster = rasterize(emoji: element)
                    return createEmitterCell(image: raster, index: offset)
                })
            emitterLayer.emitterCells = emojiCells
    

    If I had fewer emitter cells then I would include duplicate emitters around each other. Such as [cell1, cell2, cell1]. Other adjustments would help in that situation such as increasing the birth rate of cell2.