9

I would like to create a particle effect which is only emitting while the user touches the screen, but I cannot change the CAEmitterCell birthRate property once is set to a non zero value.

I have a subclass of UIView, which sets up my CAEmitterLayer and my CAEmitterCell just the way I want them. I am defining two properties on that class:

@property (strong, nonatomic) CAEmitterLayer *emitterLayer;
@property (strong, nonatomic) CAEmitterCell *emitterCell;

Then, in my view controller, I am tracking touches, setting the position of the emitterLayer, and emitterCell birthrate:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint tappedPt = [touch locationInView:touch.view];
    NSLog(@"began x:%f y:%f",tappedPt.x, tappedPt.y);
    emitterView.emitterCell.birthRate = 42;
    emitterView.emitterLayer.emitterPosition = tappedPt;
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint tappedPt = [touch locationInView:touch.view];
    NSLog(@"moved x:%f y:%f",tappedPt.x, tappedPt.y);
    emitterView.emitterLayer.emitterPosition = tappedPt;
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"ending %f", emitterView.emitterCell.birthRate);
    emitterView.emitterCell.birthRate = 0.00;
    NSLog(@"ended %f", emitterView.emitterCell.birthRate);
}

The log reports that the emitterView.emitterCell.birthRate changes:

began x:402.000000 y:398.500000
ending 42.000000
ended 0.000000

When I touch the screen, the emitter starts as expected, the layer follows the touch, but when I end the touch, the emitter cell happily emits whatever value was set initially set (the value set in touchesBegan). Whatever I do I cannot seem to be able to change the birthrate value once is set to a non zero value. Log reports that the values are set properly, but the emitter keeps emitting.

However, if I change the touchesEnded method to change the position of the layer, after I set the birthRate on emitterCell then everything works as expected:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch = [touches anyObject];
    CGPoint tappedPt = [touch locationInView:touch.view];
    NSLog(@"began x:%f y:%f",tappedPt.x, tappedPt.y);

    NSLog(@"ending %f", emitterView.emitterCell.birthRate);
    emitterView.emitterCell.birthRate = 0.0;
    NSLog(@"ended %f", emitterView.emitterCell.birthRate);
    emitterView.emitterLayer.emitterPosition = tappedPt;
}

Can someone please explain why?

nekiTamoTip
  • 173
  • 8

2 Answers2

7

To stop emitting the particles you have to set birthRate property of CAEmitterLayer instance to 0, although it was initially set on CAEmitterCell instance... Not sure why, but it works.

Swift 3 example:

func emitParticles() {
    let particlesEmitter = CAEmitterLayer()
    particlesEmitter.emitterPosition = center
    particlesEmitter.emitterShape = kCAEmitterLayerCircle
    particlesEmitter.emitterSize = CGSize(width: 50, height: 50)
    particlesEmitter.renderMode = kCAEmitterLayerAdditive

    let cell = CAEmitterCell()
    cell.birthRate = 15
    cell.lifetime = 1.0
    cell.color = bubble.color.cgColor
    cell.velocity = 150
    cell.velocityRange = 50
    cell.emissionRange = .pi
    cell.scale = 0.1
    cell.scaleSpeed = -0.1

    cell.contents = UIImage(named: "particle")?.cgImage
    particlesEmitter.emitterCells = [cell]

    layer.addSublayer(particlesEmitter)

    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        particlesEmitter.birthRate = 0
    }
}
Antonin Charvat
  • 920
  • 9
  • 16
6

I don't know why it is behaving a bit weird but I found out that using key value coding solves the issue and the cell stops emitting:

Assuming your CAEmitterLayer is "yourEmitterLayer" and you have a CAEmitterCell that is named "yourEmitterCell", this will stop the emission if placed inside touchesEnded:

[yourEmitterLayer setValue:[NSNumber numberWithInt:0] forKeyPath:@"emitterCells.yourEmitterCell.birthRate"];
Krumelur
  • 32,180
  • 27
  • 124
  • 263
  • two questions: 1) birthRate is float, you are assigning an int. 2) does the emitter manipulation always has to start from its emiter? I mean, in this case you call the emitter and pass the path `emitterCells.yourEmitterCell.birthRate`, but suppose I have a reference to the emitterCell itself. Can I simply do `[self.emiterCell .... forKeyPath:@"birthRate"]` ? – Duck Feb 18 '15 at 11:08
  • i'd also like to know the answer to the above commenter's question – Alexander Bollbach Oct 24 '15 at 21:58
  • 1
    To work this code you should set the name property for each emittercells self.yourEmitterCell.name = @"yourEmitterCell", – Anees Dec 20 '17 at 07:27