I am recieving memory warning using 100 of animating images so I tried to use Core Animation instead but that gives me the same problem. This is because I don't know how to use replaceSublayer in my current code
UIView* upwardView=[[UIView alloc]init];
[upwardView setFrame:CGRectMake(0, 0, 1024, 768)];
[self.view addSubview:upwardView];
NSArray *animationImages=[NSArray arrayWithObjects:[UIImage imageNamed:#"001.png"],[UIImage imageNamed:#"001.png"],[UIImage imageNamed:#"002.png"],[UIImage imageNamed:#"003.png"],....,nil];
CAKeyframeAnimation *animationSequence = [CAKeyframeAnimation animationWithKeyPath: #"contents"];
animationSequence.calculationMode = kCAAnimationLinear;
animationSequence.autoreverses = YES;
animationSequence.duration = 5.00;
animationSequence.repeatCount = HUGE_VALF;
NSMutableArray *animationSequenceArray = [[NSMutableArray alloc] init];
for (UIImage *image in animationImages)
{
[animationSequenceArray addObject:(id)image.CGImage];
}
CALayer *layer = [upwardView layer];
animationSequence.values = animationSequenceArray;
[layer addAnimation:animationSequence forKey:#"contents"];
I guess you need to add a few lines more. Just replace the last three lines and add the following line.
//Prepare CALayer
CALayer *layer = [CALayer layer];
layer.frame = self.view.frame;
layer.masksToBounds = YES;
[layer addAnimation:animationSequence forKey:#"contents"];
[upwardView.layer addSublayer:layer]; // Add CALayer to your desired view
For detail implementation check this reference
Related
The first time it works nice but second time when changes view constraint and again going to this function it crashes in below code.
anyone know please let me know thank you.
below code running first time perfect second time in the same view not running perfectly.
UIView *view = (UIView*)[slider.subviews objectAtIndex:0];
-(void)setGradientToSlider:(UISlider *)slider WithColors:(NSArray*)colorArray
{
UIView *view;
UIImageView *max_trackImageView;
view = (UIView*)[slider.subviews objectAtIndex:0];
max_trackImageView = (UIImageView*)[view.subviews objectAtIndex:0];
//setting gradient to max track image view.
CAGradientLayer* max_trackGradient = [CAGradientLayer layer];
CGRect rect=max_trackImageView.frame;
rect.origin.x=view.frame.origin.x;
max_trackGradient.frame=rect;
max_trackGradient.colors = colorArray;
[max_trackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[max_trackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[view.layer setCornerRadius:5.0];
[max_trackImageView.layer insertSublayer:max_trackGradient atIndex:0];
//Setting gradient to min track ImageView.
CAGradientLayer* min_trackGradient = [CAGradientLayer layer];
UIImageView *min_trackImageView;
min_trackImageView = (UIImageView*)[slider.subviews objectAtIndex:1];
rect=min_trackImageView.frame;
rect.size.width=max_trackImageView.frame.size.width;
rect.origin.y=0;
rect.origin.x=0;
min_trackGradient.frame=rect;
min_trackGradient.colors = colorArray;
[min_trackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[min_trackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[min_trackImageView.layer setCornerRadius:5.0];
[min_trackImageView.layer insertSublayer:min_trackGradient atIndex:0];
}
I've got this rather simple piece of example code in which I have a simple hierarchy consisting of three layers;
view layer
|- base layer
|- green layer
Now I want to move the base layer and at the same time adjust the color of the green layer, so I've added a slider to control the animations.
The code looks like this:
#implementation ViewController
- (void)loadView
{
[super loadView];
baseLayer = [CALayer layer];
[baseLayer setFrame:[[self view] frame]];
[baseLayer setBackgroundColor:[[[UIColor redColor] colorWithAlphaComponent:0.5] CGColor]];
[[[self view] layer] addSublayer:baseLayer];
greenLayer = [CALayer layer];
[greenLayer setBounds:[baseLayer bounds]];
[greenLayer setBackgroundColor:[[UIColor greenColor] CGColor]];
[baseLayer addSublayer:greenLayer];
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, CGRectGetHeight([[self view] bounds]) - 44.0f, CGRectGetWidth([[self view] bounds]), 44.0f)];
[slider addTarget:self action:#selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[[self view] addSubview:slider];
CABasicAnimation *changeColor = [CABasicAnimation animationWithKeyPath:#"backgroundColor"];
changeColor.fromValue = (id)[UIColor greenColor].CGColor;
changeColor.toValue = (id)[UIColor blueColor].CGColor;
changeColor.duration = 1.0; // For convenience
[greenLayer addAnimation:changeColor forKey:#"Change color"];
greenLayer.speed = 0.0; // Pause the animation
CABasicAnimation *changePosition = [CABasicAnimation animationWithKeyPath:#"position"];
changePosition.fromValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetWidth([[self view] bounds]) / 2, CGRectGetHeight([[self view] bounds]) / 2)];
changePosition.toValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetWidth([[self view] bounds]), CGRectGetHeight([[self view] bounds]) / 2)];
changePosition.duration = 1.0;
[baseLayer addAnimation:changePosition forKey:#"Change position"];
baseLayer.speed = 0.0;
}
- (void)sliderValueChanged:(UISlider *)slider
{
[baseLayer setTimeOffset:[slider value]];
[greenLayer setTimeOffset:[slider value]];
}
My question: Do I need to change the timeOffset for each layer individually (which obviously works), or am I missing something here and can I do with a more intelligent solution?
(Of course this is a simple example, but my actual hierarchy can be somewhat more complex and having an arbitrary number of layers theoretically)
It should work when you remove the speed = 0 from the sub layers, in this case the greenLayer.
The speed property works relative to parent layers so you can pause all animations by adjusting the speed on the parent layer. Speed on sublayers will work relatively to the parent layer speed.
I can draw points with code below, but I need to draw every point one by one with animation. When I try this, all points appear on the screen only after the for loop ends.
I did some research but I could not figure out why these points are not drawing one by one in the for loop. singerData is an array which has x values of points.
-(void)drawDotGraphForUser
{
UIView *viewAnimationUser = [[UIView alloc]init];
viewAnimationUser.frame = CGRectMake(0, 0, 320, 100);
for (int i = 0; i<singerPitchDataSize; i++)
{
CALayer *layer = [CALayer layer];
NSAssert1(layer != nil, #"failed to create CALayer #%i", i);
layer.backgroundColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:1.0].CGColor;
layer.frame = CGRectMake(i+1, singerData[i], 1.0, 1.0);
NSLog(#"GraphUser= %d=%f",i,singerData[i]);
NSLog(#"MSprites=%#",mSprites[i]);
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 5;
pathAnimation.fromValue = [NSNumber numberWithInt:0];
pathAnimation.toValue = [NSNumber numberWithInt:singerPitchDataSize];
[layer addAnimation:pathAnimation forKey:#"strokeEnd"];
[viewAnimationUser.layer addSublayer:layer];
[self.view addSubview:viewAnimationUser];
}
}
These are all appearing to be happening at the same time because your animations are being sent to another thread automatically extremely quickly. If you want them to appear one by one you will need to send them to add them to the subview slower by adding a timer at the end of your function or you will need to listen for the animation to end to trigger the next one.
I'm working on a component that is supposed to render several images and animate their position while changing also the contents. I've been inspired by this post: http://ronnqvi.st/controlling-animation-timing/ and thought of using speed to control the animations.
My ideia is to add the CALayers with the animations to a top-level layer whose speed is 0 and then set this layer's speed to 1.0 when it's time to animate.
Here's the code for it:
...
#property (nonatomic,strong) CALayer *animationLayer;
...
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.animationLayer = [CALayer layer];
self.animationLayer.frame = frame;
self.animationLayer.speed = 0.0;
[self.layer addSublayer:self.animationLayer];
}
return self;
}
- (void)addLayerData:(LayerData*)layerData
{
CALayer *aLayer = [CALayer layer];
aLayer.contents = (id) layerData.image.CGImage;
aLayer.frame = CGRectMake(0,0,96,96);
aLayer.position = layerData.position;
aLayer.name = layerData.name;
CGRect pathLayerFrame = layerData.path.bounds;
pathLayerFrame.origin.x = 0.0;
pathLayerFrame.origin.y = 0.0;
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = pathLayerFrame;
pathLayer.path = layerData.path.CGPath;
pathLayer.strokeColor = layerData.strokeColor.CGColor;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.lineWidth = 10;
pathLayer.lineCap = kCALineCapRound;
[self.animationLayer addSublayer:pathLayer];
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
[positionAnimation setPath: layerData.path.CGPath];
[positionAnimation setDuration: 10.0];
[positionAnimation setRemovedOnCompletion: NO];
[positionAnimation setFillMode: kCAFillModeBoth];
[positionAnimation setKeyTimes:layerData.keyTimes];
CAKeyframeAnimation *contentsAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
[contentsAnimation setValues:[self buildContentImagesFromLayerData:layerData];
[contentsAnimation setCalculationMode:kCAAnimationDiscrete];
contentsAnimation.calculationMode = kCAAnimationLinear;
[contentsAnimation setDuration:0.1];
[contentsAnimation setDelegate:self];
[contentsAnimation setAutoreverses:YES];
[contentsAnimation setRepeatCount:HUGE_VALF];
[contentsAnimation setRemovedOnCompletion:NO];
[contentsAnimation setFillMode:kCAFillModeBoth];
[aLayer addAnimation:positionAnimation forKey:kPostionAnimationKey];
[aLayer addAnimation:contentsAnimation forKey:kContentsAnimationKey];
[self.animationLayer addSublayer:aLayer];
}
- (void)animate
{
self.animationLayer.speed = 1.0;
}
My issue is that although the animationLayer contains all the layers to animate and these have all the animations correctly defined I can't seem to get the layers moving around the screen.
The addLayerData is called several times by external client code, at different times (that is when user taps a button to add an image). The animate is called after that when the user taps a button.
Maybe I've missed something in the article.
Can anyone help me out on this one?
Thanks in advance.
UPDATE
If I drop the animationLayer and add the layers to be animated directly on the view's layer (self.layer) it works correctly.
I seem to be missing the obvious when animating a key frame. I have looked at many code samples including Apple's MoveMe, referenced in the CAKeyframeAnimation documentation, yet, I cant find a discrepancy that would cause what I'm seeing.
I create a CGMutablePathRef, then a CAKeyframeAnimation and set it to animate an image view along the path. An animation group is created so I can remove the view when done.
Yet, my animation never shows up. UNTIL I rotate the device. It seems a relayout of the view causes the animation to kickstart. I tried the obvious like [theImageView setNeedsDisplay] or even setNeedsLayout, and on the container view as well. Yet, still cant get it to work when I need to. They only show up when I rotate the device.
In the following, -cgPathFromArray: takes an NSArray of internal opcodes which is converted into a CGPathRef. Its verified to be correct because when I rotate the device, the animation does show along the programmed path.
- (void) animateImage: (NSString*) imageName
onPath: (NSArray*) path
withDuration: (NSString*) duration
{
if (self.sceneView)
{
CGMutablePathRef animationPath = [self cgPathFromArray: path];
if (animationPath)
{
UIImage* image = [self findImage: imageName];
if (image)
{
UIImageView* imageView = [[UIImageView alloc] initWithFrame: CGRectMake(0, 0, image.size.width, image.size.height)];
if (imageView)
{
CAKeyframeAnimation* keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
imageView.image = image;
[self.sceneView addSubview: imageView];
keyFrameAnimation.removedOnCompletion = YES;
keyFrameAnimation.fillMode = kCAFillModeForwards;
keyFrameAnimation.duration = duration.floatValue;
keyFrameAnimation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
keyFrameAnimation.repeatCount = 0;
keyFrameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[keyFrameAnimation setPath: animationPath];
//group animation with termination block to cleanup
CAAnimationGroup* group = [CAAnimationGroup animation];
group.duration = keyFrameAnimation.duration;
group.removedOnCompletion = YES;
group.fillMode = kCAFillModeForwards;
group.animations = #[keyFrameAnimation];
CorpsAnimationCompletionBlock theBlock = ^void(void)
{
[imageView removeFromSuperview];
};
[group setValue: theBlock
forKey: kCorpsAnimationCompletionBlock];
group.delegate = self;
[imageView.layer addAnimation: group
forKey: nil];
}
}
}
}
}
Anyone can help with this?
You are probably having trouble because you're adding an animation to a layer in the same transaction where the layer is added to the visible layer tree. Core Animation doesn't like to attach animations to layers that haven't been committed yet. You may be able to work around this by doing [CATransaction flush] after adding the layer.
Your code is rather hard to look at because of the excessive nesting. Consider using early returns to make it more readable.
Also, you're explicitly creating the same frame that the -[UIImage initWithImage:] initializer would create for you.
If you're using an animation group and setting a delegate simply so you can execute a block at the end of the animation, there is an easier way. You can begin a CATransaction, set the transaction's completion block, then add the animation, then commit the transaction.
Thus:
- (void) animateImage:(NSString *)imageName onPath: (NSArray *)path
withDuration: (NSString *)duration
{
if (!self.sceneView)
return;
CGMutablePathRef animationPath = [self cgPathFromArray:path];
if (!animationPath)
return;
UIImage *image = [self findImage:imageName];
if (!image)
return;
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.sceneView addSubview: imageView];
// commit the implicit transaction so we can add an animation to imageView.
[CATransaction flush];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[imageView removeFromSuperview];
}];
CAKeyframeAnimation *animation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
animation.duration = duration.floatValue;
animation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
animation.path = animationPath;
[imageView.layer addAnimation:animation forKey:animation.keyPath];
} [CATransaction commit];
}