38
votes

I'm trying to set up a CAEmitterLayer to make a confetti effect, and I've run into two issues:

  1. Whenever I set the birthRate on my cells to something non-zero to start the animation I get a flurry of cells placed randomly on screen, which animate normally, and then the emitter continues to emit properly after that.
  2. Whenever the emitterCells are drawing things on screen, any time I touch the screen, the emitter draws emitterCells in (seemingly) random locations that exist for a (seemingly) random amount of time. Nothing in the emitter is tied to any touch events (i.e. I'm not intentionally drawing anything on a touch event), but the layer is in a view that has multiple embedded views. The more I touch, the more cells show up.

Here's my code for setting up the emitter, and then starting and stopping it (once I've called the stop function, then taps on the screen cease creating new random elements):

- (void)setupConfetti
{
    self.confettiLayer = [CAEmitterLayer layer];
    [self.view.layer addSublayer:self.confettiLayer];
    [self.view.layer setNeedsDisplay];

    self.confettiLayer.emitterPosition = CGPointMake(1024.0/2,-50.0);
    self.confettiLayer.emitterSize = CGSizeMake(1000.0, 10.0);
    self.confettiLayer.emitterShape = kCAEmitterLayerLine; 
    self.confettiLayer.renderMode =kCAEmitterLayerUnordered;

    CAEmitterCell *confetti = [CAEmitterCell emitterCell];

    confetti1.contents =  (id)[[UIImage imageNamed:@"confetti.png"] CGImage];

    confetti.emissionLongitude = M_PI;
    confetti.emissionLatitude = 0;
    confetti.lifetime = 5;
    confetti.birthRate = 0.0;
    confetti.velocity = 125;
    confetti.velocityRange = 50;
    confetti.yAcceleration = 50;
    confetti.spin = 0.0;
    confetti.spinRange = 10;
    confetti.name = @"confetti1";

    self.confettiLayer.emitterCells = [NSArray arrayWithObjects:confetti, nil];
}

To start the confetti:

- (void)startConfettiAnimation
{
    [self.confettiLayer setValue:[NSNumber numberWithInt:10.0] forKeyPath:@"emitterCells.confetti.birthRate"];
}

And to stop it:

- (void)stopConfettiAnimation
{
    [self.confettiLayer setValue:[NSNumber numberWithInt:0.0] forKeyPath:@"emitterCells.confetti.birthRate"];
}

Again, once it gets started, after the initial flurry of random elements, this works just fine: everything animates normally, and when the birthRate is later set to zero, it ends gracefully. It just seems to respond to touch events, and I have no idea why. I've tried adding the emitterLayer to a different view, disabling user interaction on that view, and then adding it as a subview of the main view, and that didn't seem to work.

Any help/insight would be much appreciated!

Thanks, Sam

3
Did you find out anything about this? I'm having the same problem too. Seems like a very unnecessary "feature" to me.John Estropia
Maybe after one year since last comment in this post somebody did find the solution of this problem? I see this issue in every project founded in the web that is using CAEmitterLayer class. You can try even with this tutorial: raywenderlich.com/6063/uikit-particle-systems-in-ios-5-tutorial. Just remove touches handling from vc and reduce birthRate to 1 for example, and start taping anywhere. All live particles seems to be doubled after tap. The problem appears only on devices, not in simulator.Artur Ozierański

3 Answers

7
votes

I know this is an old post, but I also had this problem. Jackslash answers it well in this post: iOS 7 CAEmitterLayer spawning particles inappropriately

You need to set beginTime on your emitter layer to begin at the current time with CACurrentMediaTime(). It seems the problem we have occurs because the emitter started already in the past.

emitter.beginTime = CACurrentMediaTime();
1
votes

Could it be that you aren't checking to see if the particle is emitting like in the Wenderlich example Artur Ozieranski posted? I'm not seeing the doubling as long as the check is in place.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setEmitterPositionFromTouch: [touches anyObject]];
    [fireView setIsEmitting:YES];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setIsEmitting:NO];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setIsEmitting:NO];
}
-(void)setIsEmitting:(BOOL)isEmitting
{
    //turn on/off the emitting of particles
    [fireEmitter setValue:[NSNumber numberWithInt:isEmitting?200:0] forKeyPath:@"emitterCells.fire.birthRate"];
}
0
votes

.h file

 #import <UIKit/UIKit.h>

    @interface DWFParticleView : UIView

    -(void)setEmitterPositionFromTouch: (CGPoint*)t;
    -(void)setIsEmitting:(BOOL)isEmitting;

    @end

.m file

  #import "DWFParticleView.h"
    #import <QuartzCore/QuartzCore.h>

    @implementation DWFParticleView
    {
        CAEmitterLayer* fireEmitter; //1
    }

    -(void)awakeFromNib
    {
        //set ref to the layer
        fireEmitter = (CAEmitterLayer*)self.layer; //2
        //configure the emitter layer
        fireEmitter.emitterPosition = CGPointMake(50, 50);
        fireEmitter.emitterSize = CGSizeMake(10, 10);

        CAEmitterCell* fire = [CAEmitterCell emitterCell];
        fire.birthRate = 0;
        fire.lifetime = 1.5;
        fire.lifetimeRange = 0.3;
        fire.color = [[UIColor colorWithRed:255 green:255 blue:255 alpha:0.1] CGColor];
        fire.contents = (id)[[UIImage imageNamed:@"Particles_fire.png"] CGImage];
        [fire setName:@"fire"];

        fire.velocity =5;
        fire.velocityRange = 20;
        fire.emissionRange = M_PI_2;

        fire.scaleSpeed = 0.1;
        fire.spin = 0.5;

        fireEmitter.renderMode = kCAEmitterLayerAdditive;

        //add the cell to the layer and we're done
        fireEmitter.emitterCells = [NSArray arrayWithObject:fire];

    }

    + (Class) layerClass //3
    {
        //configure the UIView to have emitter layer
        return [CAEmitterLayer class];
    }

    -(void)setEmitterPositionFromTouch: (CGPoint*)t
    {
        //change the emitter's position
        fireEmitter.emitterPosition = (*t);
    }

    -(void)setIsEmitting:(BOOL)isEmitting
    {
        //turn on/off the emitting of particles
        [fireEmitter setValue:[NSNumber numberWithInt:isEmitting?100:0] forKeyPath:@"emitterCells.fire.birthRate"];
    }


    @end

I used this code for create a custom view and to emit particles on touch

Here is the call statement for emission of particle on touch

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint p =  [[touches anyObject] locationInView:self.view];
     [fireView setEmitterPositionFromTouch: &p];
     [fireView setIsEmitting:YES];

}

may be it will work for you .