ios -- How to limit extent of CAEmitter particle trajectories? - ios

I successfully added a CAEmitter to my UIView inside a subview, but the particles emitted by that layer extend outside that view. Please see code below for the cookiecutter code and parameters. My question is how I can prevent the generated particles from exiting the parent view?
I've tried initializing the subview with a certain frame, and even resizing the bounds. In both cases, the emitted particles start inside the bounds but fly outside the view frame, seemingly without bound. They don't disappear until I limit the lifetime, or until they reach the end of the screen. Is there a way to prevent emitters from interacting with objects outside the view?
CAEmitterLayer *emitterLayer = (CAEmitterLayer*)self.layer;
emitterLayer.name = #"emitterLayer";
emitterLayer.emitterPosition = CGPointMake(self.frame.size.width/2, 0);
emitterLayer.emitterZPosition = 0;
emitterLayer.emitterSize = CGSizeMake(1.00, 1.00);
emitterLayer.emitterDepth = 0.00;
emitterLayer.emitterShape = kCAEmitterLayerCuboid;
emitterLayer.renderMode = kCAEmitterLayerAdditive;
emitterLayer.seed = 3534563912;
// Create the emitter Cell
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.name = #"StatSap";
emitterCell.enabled = YES;
emitterCell.contents = (id)[[UIImage imageNamed:#"mysprite.png"] CGImage];
emitterCell.contentsRect = CGRectMake(0.00, 0.00, 1.00, 1.00);
emitterCell.magnificationFilter = kCAFilterTrilinear;
emitterCell.minificationFilter = kCAFilterLinear;
emitterCell.minificationFilterBias = 0.00;
emitterCell.scale = 1.00;
emitterCell.scaleRange = 0.00;
emitterCell.scaleSpeed = -0.24;
emitterCell.color = [[UIColor colorWithRed:1.00 green:0.28 blue:0.26 alpha:1.00] CGColor];
emitterCell.redRange = 1.00;
emitterCell.greenRange = 0.34;
emitterCell.blueRange = 0.31;
emitterCell.alphaRange = 0.46;
emitterCell.redSpeed = 2.03;
emitterCell.greenSpeed = 0.00;
emitterCell.blueSpeed = 0.00;
emitterCell.alphaSpeed = 0.00;
emitterCell.lifetime = 2.65;
emitterCell.lifetimeRange = 1.02;
emitterCell.birthRate = 267;
emitterCell.velocity = 155.32;
emitterCell.velocityRange = 25.00;
emitterCell.xAcceleration = 0.00;
emitterCell.yAcceleration = 911.00;
emitterCell.zAcceleration = 0.00;
// these values are in radians, in the UI they are in degrees
emitterCell.spin = -0.175;
emitterCell.spinRange = 12.566;
emitterCell.emissionLatitude = 0.000;
emitterCell.emissionLongitude = 0.000;
emitterCell.emissionRange = 6.283;
emitterLayer.emitterCells = #[emitterCell];

Have you made sure that the superview of the emitter has clipsToBounds = YES?

Related

Can SKEmitterNode particles be rotated around their X and Y axes? [duplicate]

I'm trying to reproduce pieces of small paper falling from top effect, using CAEmitterLayer & CAEmitterCell.
So far, I got the 2D animation of it, But I'm having difficulty to make each cell to rotate when falling.
How to I apply random rotation on each particle? I tried with 3D Transform with no success so far.
This is what I got:
-(void) configureEmitterLayer {
self.emitterLayer = [CAEmitterLayer layer];
self.emitterLayer.emitterPosition = CGPointMake(self.backgroundContainer.bounds.size.width /2, 0);
self.emitterLayer.emitterZPosition = 10;
self.emitterLayer.emitterSize = self.backgroundContainer.bounds.size;
self.emitterLayer.emitterShape = kCAEmitterLayerLine;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.contents = (__bridge id)([UIImage imageWithColor:[UIColor whiteColor]].CGImage);
emitterCell.lifetime = CGFLOAT_MAX;
emitterCell.lifetimeRange = 4.0;
emitterCell.birthRate = 2.5;
emitterCell.color = [[UIColor colorWithRed:1.0f green:1.0 blue:1.0 alpha:1.0] CGColor];
emitterCell.redRange = 1.0;
emitterCell.blueRange = 1.0;
emitterCell.greenRange = 1.0;
emitterCell.alphaRange = 0.3;
emitterCell.velocity = 10;
emitterCell.velocityRange = 3;
emitterCell.emissionRange = (CGFloat) M_PI_2;
emitterCell.emissionLongitude = (CGFloat) M_PI;
emitterCell.yAcceleration = 1;
emitterCell.zAcceleration = 4;
emitterCell.spinRange = 2.0;
emitterCell.scale = 7.0;
emitterCell.scaleRange = 4.0;
self.emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
[self.backgroundContainer.layer addSublayer:self.emitterLayer];
}
This library doesn't use the emitter framework but is worth looking at for inspiration. UIDynamics is attached to individual objects to achieve the desired effect.
iOS confetti example

Round button with border and shadow

What would be the best way to create a button such as the one in the image?
I tried:
self.button.layer.shadowColor = UIColor.redColor.CGColor;
self.button.layer.shadowRadius = 30.0f;
self.button.layer.shadowOpacity = 1.0f;
self.button.layer.shadowOffset = CGSizeMake(0.0f, 30.0f);
self.button.clipsToBounds = NO;
[self.button setNeedsDisplay];
But this just add a minor very blury shadow not at all like in the image.
Hey #user426132 set UIbutton background image and Try this code
For Objective-c
self.button.layer.cornerRadius = self.button.frame.size.width / 2;
self.button.layer.masksToBounds = false;
self.button.layer.shadowColor = UIColor.grayColor.CGColor;
self.button.layer.shadowOpacity = 1.0;
self.button.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.button.layer.shadowRadius = 10;
self.button.layer.shouldRasterize = true;
self.button.layer.rasterizationScale = UIScreen.mainScreen.scale;
For Swift 4.2
self.btnShadow.layer.cornerRadius = self.btnShadow.frame.size.width / 2
self.btnShadow.layer.masksToBounds = false
self.btnShadow.layer.shadowColor = UIColor.black.cgColor
self.btnShadow.layer.shadowOpacity = 1.0
self.btnShadow.layer.shadowOffset = CGSize(width: 0, height: 0)
self.btnShadow.layer.shadowRadius = 10
self.btnShadow.layer.shouldRasterize = true
self.btnShadow.layer.rasterizationScale = UIScreen.main.scale
Output is here in black cross is image

CAEmitterLayer not Shown

I'm simply adding a UIView to my storyboard view controller and then creating a UIView class to display a particle effect. I change the UIView's class name to that of the custom class I created. The code I'm using in the UIView class does not display the particle effect I expected. The code is as follows:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGRect viewBounds = rect;
fireworksEmitter.emitterPosition = CGPointMake(viewBounds.size.width/2.0, viewBounds.size.height);
fireworksEmitter.emitterSize = CGSizeMake(viewBounds.size.width/2.0, 0.0);
fireworksEmitter.emitterMode = kCAEmitterLayerOutline;
fireworksEmitter.emitterShape = kCAEmitterLayerLine;
fireworksEmitter.renderMode = kCAEmitterLayerAdditive;
fireworksEmitter.seed = (arc4random()%100)+1;
// Create the rocket
CAEmitterCell* rocket = [CAEmitterCell emitterCell];
rocket.birthRate = 1.0;
rocket.emissionRange = 0.25 * M_PI; // some variation in angle
rocket.velocity = 380;
rocket.velocityRange = 100;
rocket.yAcceleration = 75;
rocket.lifetime = 1.02; // we cannot set the birthrate < 1.0 for the burst
rocket.contents = (id) [[UIImage imageNamed:#"DazRing"] CGImage];
rocket.scale = 0.2;
rocket.color = [[UIColor redColor] CGColor];
rocket.greenRange = 1.0; // different colors
rocket.redRange = 1.0;
rocket.blueRange = 1.0;
rocket.spinRange = M_PI; // slow spin
// the burst object cannot be seen, but will spawn the sparks
// we change the color here, since the sparks inherit its value
CAEmitterCell* burst = [CAEmitterCell emitterCell];
burst.birthRate = 1.0; // at the end of travel
burst.velocity = 0;
burst.scale = 2.5;
burst.redSpeed =-1.5; // shifting
burst.blueSpeed =+1.5; // shifting
burst.greenSpeed =+1.0; // shifting
burst.lifetime = 0.35;
// and finally, the sparks
CAEmitterCell* spark = [CAEmitterCell emitterCell];
spark.birthRate = 400;
spark.velocity = 125;
spark.emissionRange = 2* M_PI; // 360 deg
spark.yAcceleration = 75; // gravity
spark.lifetime = 3;
spark.contents = (id) [[UIImage imageNamed:#"DazStarOutline"] CGImage];
spark.scaleSpeed =-0.2;
spark.greenSpeed =-0.1;
spark.redSpeed = 0.4;
spark.blueSpeed =-0.1;
spark.alphaSpeed =-0.25;
spark.spin = 2* M_PI;
spark.spinRange = 2* M_PI;
// putting it together
fireworksEmitter.emitterCells = [NSArray arrayWithObject:rocket];
rocket.emitterCells = [NSArray arrayWithObject:burst];
burst.emitterCells = [NSArray arrayWithObject:spark];
[self.layer addSublayer:fireworksEmitter];
[self setNeedsDisplay];
}
1) Remove all of this code from drawRect, and put it into the UIView subclass init method, or, since you're using storyboard, put it into awakeFromNib:
- (void)awakeFromNib {
[super awakeFromNib];
CGRect viewBounds = rect;
fireworksEmitter = (CAEmitterLayer*)self.layer;
fireworksEmitter.emitterPosition = CGPointMake(viewBounds.size.width/2.0, viewBounds.size.height);
fireworksEmitter.emitterSize = CGSizeMake(viewBounds.size.width/2.0, 0.0);
fireworksEmitter.emitterMode = kCAEmitterLayerOutline;
fireworksEmitter.emitterShape = kCAEmitterLayerLine;
fireworksEmitter.renderMode = kCAEmitterLayerAdditive;
fireworksEmitter.seed = (arc4random()%100)+1;
// Create the rocket
CAEmitterCell* rocket = [CAEmitterCell emitterCell];
rocket.birthRate = 1.0;
rocket.emissionRange = 0.25 * M_PI; // some variation in angle
rocket.velocity = 380;
rocket.velocityRange = 100;
rocket.yAcceleration = 75;
rocket.lifetime = 1.02; // we cannot set the birthrate < 1.0 for the burst
rocket.contents = (id) [[UIImage imageNamed:#"DazRing"] CGImage];
rocket.scale = 0.2;
rocket.color = [[UIColor redColor] CGColor];
rocket.greenRange = 1.0; // different colors
rocket.redRange = 1.0;
rocket.blueRange = 1.0;
rocket.spinRange = M_PI; // slow spin
CAEmitterCell* burst = [CAEmitterCell emitterCell];
burst.birthRate = 1.0; // at the end of travel
burst.velocity = 0;
burst.scale = 2.5;
burst.redSpeed =-1.5; // shifting
burst.blueSpeed =+1.5; // shifting
burst.greenSpeed =+1.0; // shifting
burst.lifetime = 0.35;
CAEmitterCell* spark = [CAEmitterCell emitterCell];
spark.birthRate = 400;
spark.velocity = 125;
spark.emissionRange = 2* M_PI; // 360 deg
spark.yAcceleration = 75; // gravity
spark.lifetime = 3;
spark.contents = (id) [[UIImage imageNamed:#"DazStarOutline"] CGImage];
spark.scaleSpeed =-0.2;
spark.greenSpeed =-0.1;
spark.redSpeed = 0.4;
spark.blueSpeed =-0.1;
spark.alphaSpeed =-0.25;
spark.spin = 2* M_PI;
spark.spinRange = 2* M_PI;
fireworksEmitter.emitterCells = [NSArray arrayWithObject:rocket];
rocket.emitterCells = [NSArray arrayWithObject:burst];
burst.emitterCells = [NSArray arrayWithObject:spark];
}
Your code looks fine, so here are a few suggestions
you don't show where you are creating fireworksEmitter. If you do not alloc and init it somewhere, you will get a blank screen with no errors or warnings.
if you do not have (alpha-channel) assets named "DazRing" and "DazStarOutline" (check spelling), you will get a blank screen with no errors or warnings
Although this code will run from inside drawRect, this is not an appropriate place for it. You are not doing any drawing (directly) - rather you are setting up and adding a layer. And you certainly shouldn't be calling setNeedsDisplay from inside drawRect. This code will work just as well or better if you put it (for example) in awakeFromNib.

CAEmitterCell - slow stars or light effect

I'm playing around with CAEmitterCell, but most effects are very fast effects like the firework.
But I want to have a slow "stars" effect like you see here on www.findyourwaytooz.com
How can I do that with CAEmitterCell?
Thank you :)
I have a project that uses the following setup for the emitter, and it pretty accurately mimics what I think you mean:
//set ref to the layer
starsEmitter = (CAEmitterLayer*)self.layer; //2
//configure the emitter layer
starsEmitter.emitterPosition = CGPointMake(160, 240);
starsEmitter.emitterSize = CGSizeMake(self.superview.bounds.size.width,self.superview.bounds.size.height);
NSLog(#"width = %f, height = %f", starsEmitter.emitterSize.width, starsEmitter.emitterSize.height);
starsEmitter.renderMode = kCAEmitterLayerPoints;
starsEmitter.emitterShape = kCAEmitterLayerRectangle;
starsEmitter.emitterMode = kCAEmitterLayerUnordered;
CAEmitterCell* stars = [CAEmitterCell emitterCell];
stars.birthRate = 0;
stars.lifetime = 10;
stars.lifetimeRange = 0.5;
stars.color = [[UIColor colorWithRed:255 green:255 blue:255 alpha:0] CGColor];
stars.contents = (id)[[UIImage imageNamed:#"particle.png"] CGImage];
stars.velocityRange = 500;
stars.emissionRange = 360;
stars.scale = 0.2;
stars.scaleRange = 0.1;
stars.alphaRange = 0.3;
stars.alphaSpeed = 0.5;
[stars setName:#"stars"];
//add the cell to the layer and we're done
starsEmitter.emitterCells = [NSArray arrayWithObject:stars];
I uploaded the sample project to GitHub: SimpleCAEmitterLayer

How do you remove a CAEmitterLayer when it ends the lifetime of it's CAEmitter Cells -- instead of repeating until you remove it from super layer

I am using generic code (from the iOS Fireworks demo) in a slightly changed way. I have the following in a subclass of UIView. What I want is to make the firework appear at the point the user touches (not hard) and play out for the length of the CAEmitterLayer/CAEmitterCells 'lifetime'. Instead, this is immediately starting when i add it to addSublayer -- like I am sure it is meant to. However, I would like to use it in a slightly different way. Is there a way that I can change this so there is a CATransaction with a completion block (to removeFromSuperlayer) or something like that? Any ideas are welcomed.
#import "FireworksView.h"
#implementation FireworksView
- (void)launchFirework{
//Load the spark image for the particle
CAEmitterLayer *mortor = [CAEmitterLayer layer];
mortor.emitterPosition = CGPointMake(self.bounds.size.width/2, self.bounds.size.height*(.75));
mortor.renderMode = kCAEmitterLayerAdditive;
//Invisible particle representing the rocket before the explosion
CAEmitterCell *rocket = [CAEmitterCell emitterCell];
rocket.emissionLongitude = -M_PI / 2;
rocket.emissionLatitude = 0;
rocket.lifetime = 1.6;
rocket.birthRate = 1;
rocket.velocity = 400;
rocket.velocityRange = 100;
rocket.yAcceleration = 250;
rocket.emissionRange = M_PI / 4;
rocket.color = CGColorCreateCopy([UIColor colorWithRed:.5 green:.5 blue:.5 alpha:.5].CGColor);
rocket.redRange = 0.5;
rocket.greenRange = 0.5;
rocket.blueRange = 0.5;
//Name the cell so that it can be animated later using keypath
[rocket setName:#"rocket"];
//Flare particles emitted from the rocket as it flys
CAEmitterCell *flare = [CAEmitterCell emitterCell];
flare.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
flare.emissionLongitude = (4 * M_PI) / 2;
flare.scale = 0.4;
flare.velocity = 100;
flare.birthRate = 45;
flare.lifetime = 1.5;
flare.yAcceleration = 350;
flare.emissionRange = M_PI / 7;
flare.alphaSpeed = -0.7;
flare.scaleSpeed = -0.1;
flare.scaleRange = 0.1;
flare.beginTime = 0.01;
flare.duration = 0.7;
//The particles that make up the explosion
CAEmitterCell *firework = [CAEmitterCell emitterCell];
firework.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
firework.birthRate = 9999;
firework.scale = 0.6;
firework.velocity = 130;
firework.lifetime = 2;
firework.alphaSpeed = -0.2;
firework.yAcceleration = 80;
firework.beginTime = 1.5;
firework.duration = 0.1;
firework.emissionRange = 2 * M_PI;
firework.scaleSpeed = -0.1;
firework.spin = 2;
//Name the cell so that it can be animated later using keypath
[firework setName:#"firework"];
//preSpark is an invisible particle used to later emit the spark
CAEmitterCell *preSpark = [CAEmitterCell emitterCell];
preSpark.birthRate = 80;
preSpark.velocity = firework.velocity * 0.70;
preSpark.lifetime = 1.7;
preSpark.yAcceleration = firework.yAcceleration * 0.85;
preSpark.beginTime = firework.beginTime - 0.2;
preSpark.emissionRange = firework.emissionRange;
preSpark.greenSpeed = 100;
preSpark.blueSpeed = 100;
preSpark.redSpeed = 100;
//Name the cell so that it can be animated later using keypath
[preSpark setName:#"preSpark"];
//The 'sparkle' at the end of a firework
CAEmitterCell *spark = [CAEmitterCell emitterCell];
spark.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
spark.lifetime = 0.05;
spark.yAcceleration = 250;
spark.beginTime = 0.8;
spark.scale = 0.4;
spark.birthRate = 10;
preSpark.emitterCells = [NSArray arrayWithObjects:spark, nil];
rocket.emitterCells = [NSArray arrayWithObjects:flare, firework, preSpark, nil];
mortor.emitterCells = [NSArray arrayWithObjects:rocket, nil];
[self.layer addSublayer:mortor];
}
The answer to this is, using CAEmitter, there is NO WAY -- delegate, etc -- to stop the emitter when it ends the cycle. The only thing you can do is gracefully remove it from the layer when you think it should be removed.
Ok, so I was able to sort of achieve this by creating an animation with delegate that fired along with the emitter. in the animationDidStop i made a for loop that went through my view hierarchy and looked for emitters and removed them. it's buggy and I still want a real solution, but this works for now.
for (CALayer *layer in _plusButton.layer.sublayers) {
if (layer.class == [CAEmitterLayer class]) {
[layer removeFromSuperlayer];
}
}

Resources