CAShapeLayer Path Animation - objective-c

I'm trying to animate a path change on a particular shape. I can manually change without animation by replacing this code with a simple .path [cgpath] setter. However, I want to animate the change for aesthetics.
When this code executes, there is no change to the shape what-so-ever although the log suggests the animation is complete.
for (CALayer *layer in self.view.layer.sublayers) {
if ([layer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *copy = (CAShapeLayer *)layer;
if ([sideChoice isEqualToString:#"datum"]) {
if ([copy.name isEqualToString:#"datumSideLayer"]) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"Animation complete.");
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.toValue = pathChange;
[copy addAnimation:morph forKey:nil];
[CATransaction commit];
break;
}
} else if ([sideChoice isEqualToString:#"opposite"]) {
if ([copy.name isEqualToString:#"oppositeSideLayer"]) {
}
}
}
}

for (CALayer *layer in self.view.layer.sublayers) {
if ([layer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *copy = (CAShapeLayer *)layer;
if ([sideChoice isEqualToString:#"datum"]) {
if ([copy.name isEqualToString:#"datumSideLayer"]) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"Animation complete.");
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.fromValue = (id) copy.path;
morph.toValue = (id) pathChange.CGPath;
[copy addAnimation:morph forKey:nil];
copy.path = [pathChange CGPath];
[CATransaction commit];
break;
}
} else if ([sideChoice isEqualToString:#"opposite"]) {
if ([copy.name isEqualToString:#"oppositeSideLayer"]) {
}
}
}
}
I didn't correctly set a fromValue nor was I pointing to a CGPath.

Related

How can I remove or hide a sublayer?

-(IBAction)displayinfo:(id)sender
{
sublayer = [CALayer layer];
if (appear == NO)
{
appear = YES;
sublayer.contents=(id)[UIImage imageNamed:#"infoPalette.png"].CGImage;
sublayer.frame= CGRectMake(300,200,350,250);
[self.view.layer addSublayer:sublayer];
}
else
{
[sublayer removeFromSuperlayer];
}
}
This allows the layer to appear but I can't remove it or hide it upon clicking the same button.
Replace your existing code with this one
-(IBAction)displayinfo:(id)sender
{
if ( appear == NO)
{
sublayer = [CALayer layer];
appear = YES;
sublayer.contents=(id)[UIImage imageNamed:#"infoPalette.png"].CGImage;
sublayer.frame= CGRectMake(300,200,350,250);
[self.view.layer addSublayer:sublayer];
}
else
{
[sublayer removeFromSuperlayer];
}
}
In my case where I added multiple sublayers to my background imageView, I removed all of them using
for (int i = 0 ; i < [self.backgroundImageView.layer.sublayers count]; i++ ) {
[[self.backgroundImageView.layer.sublayers objectAtIndex:i] removeFromSuperlayer];
}
Note: I also iterated like this
for (CALayer *layer in self.backgroundImageView.layer.sublayers) {
[layer removeFromSuperlayer];
}
but this results in BadAccess, probably the reason that I added some CAGradientLayer to backgroundImageView.Layer. (it doesn't make much sense though).
You change the code to be
- (IBAction)displayinfo:(id)sender{
if (appear == NO){
sublayer = [CALayer layer];
appear = YES;
sublayer.contents=(id)[UIImage imageNamed:#"infoPalette.png"].CGImage;
sublayer.frame= CGRectMake(300,200,350,250);
[self.view.layer addSublayer:sublayer];
}
else {
[sublayer removeFromSuperlayer];
}}
Sorry!, repeated solution

Change CALayer color while animating

APPROACH 1
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
[CATransaction begin];
{
[CATransaction setAnimationDuration:15];//Dynamic Duration
[CATransaction setCompletionBlock:^{
}];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.fromValue = #0;
animation.toValue = #1;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[self.pathLayer addAnimation:animation forKey:animationKey];
}
[CATransaction commit];
I have added CAShapeLayer (pathLayer) in my view and I want it to animate around the view with stroke effect, the code above does the job but my problem is to change color in 3 equal proportions. So what I am assuming is to repeat the above code 3 times and change the following lines in respective order.
for 1st
animation.fromValue = #0;
animation.toValue = #(1/3);
animation.timeOffset = 0;
for 2nd
animation.fromValue = #(1/3);
animation.toValue = #(2/3);
animation.timeOffset = 0;// I don't know how to exactly set this active local
time since the duration which is currently 15 is dynamic can be 30 or 10.
for 3rd
animation.fromValue = #(2/3);
animation.toValue = #(3);
animation.timeOffset = 0;// Active local time- Not sure how and which value to set
APPROACH 2
Instead of 3 transactions with offset technique lets start 2nd transaction when 1st completes and 3rd when 2nd. But the fraction of time that is taken to start the new animation when one is completed a lag/jerk is visible.
APPROACH 3
SubClass CAShapeLayer
By doing SubClass, the drawInContext method is called only once, and if some extra property is added and it is changed the drawInContext method is called repeatedly and this way the layer color can be changed after specific progress period of time.
But overriding the drawInContext method doesn't serve the purpose.
Any Suggestions ? I don't want to implement NSTimer separately.
I'm not 100% clear on what you want here, but if the goal is just for the whole stroke to change color as it draws, but in three discrete stages, then I would propose adding the following. I cooked up these examples in a default "Single View Application" template. I've got a button set up with its action pointing at -doStuff:. If the whole stroke color were to change, it might look something like this:
To produce that, the code looked like:
#implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.strokeEnd = 0.0;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];// Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(#"Done"); }];
const double portion = 1.0 / ((double)numSegments);
NSMutableArray* values = [NSMutableArray arrayWithCapacity: numSegments];
NSMutableArray* times = [NSMutableArray arrayWithCapacity: numSegments + 1];
for (NSUInteger i = 0; i < numSegments; i++)
{
[values addObject: (__bridge id)[[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor]];
[times addObject: #(i * portion)];
}
[times addObject: #(1.0)]; // Have to add this, otherwise the last value wont get used.
{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: #"strokeColor"];
animation.keyTimes = times;
animation.values = values;
animation.calculationMode = kCAAnimationDiscrete;
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: #"strokeColor"];
}
{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath: #"strokeEnd"];
animation.fromValue = #(0);
animation.toValue = #(1);
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: #"strokeEnd"];
}
}
[CATransaction commit];
}
#end
Alternately, if the goal is to have three different segments of the stroke, all with different colors, that's a little more complicated, but can still be done with the same basic principals. One thing to note is that, without custom drawing, your CAShapeLayers can't have more than one stroke color (AFAIK), so you'll need to break this up into several sublayers.
This next example puts a shape layer into the view and then adds the sublayers for each part of the stroke and sets up the animation such that it appears theres a single, multi-color stroke being drawn, where each segment is a separate color. Here's roughly what it looked like:
Here's the code:
#implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];//Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(#"Done"); }];
const double portion = 1.0 / ((double)numSegments);
for (NSUInteger i = 0; i < numSegments; i++)
{
CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
strokePart.fillColor = [[UIColor clearColor] CGColor];
strokePart.frame = mLayer.bounds;
strokePart.path = mLayer.path;
strokePart.lineCap = mLayer.lineCap;
strokePart.lineWidth = mLayer.lineWidth;
// These could come from an array or whatever, this is just easy...
strokePart.strokeColor = [[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor];
strokePart.strokeStart = i * portion;
strokePart.strokeEnd = (i + 1) * portion;
[mLayer addSublayer: strokePart];
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: #"strokeEnd"];
NSArray* times = #[ #(0.0), // Note: This works because both the times and the stroke start/end are on scales of 0..1
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(1.0) ];
NSArray* values = #[ #(strokePart.strokeStart),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(strokePart.strokeEnd) ];
animation.keyTimes = times;
animation.values = values;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[strokePart addAnimation: animation forKey: #"whatever"];
}
}
[CATransaction commit];
}
#end
I'm not sure I've exactly understood what you were going for, but hopefully one of these is helpful.

how to add time duration when doing flip using CATransform3DRotate

Thanks for the help from my first post at here so that I can do a vertical flip and the code looks like
#interface ViewController (){
CALayer *plane;
}
#end
#implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
[self addALayer];
}
- (void)addALayer{
plane = [CALayer layer];
plane.backgroundColor = [[UIColor orangeColor] CGColor];
//[plane insertSublayer:normalBackground atIndex:0];
plane.opacity = 1;
plane.frame = CGRectMake(0, 0, 300, 100);
plane.position = CGPointMake(250, 150);
plane.anchorPoint = CGPointMake(0.5, 0.5);
plane.borderColor = [UIColor whiteColor].CGColor;
plane.borderWidth = 3;
plane.cornerRadius = 10;
[self.view.layer addSublayer:plane];
}
- (IBAction)click:(id)sender {
BOOL isClicked = ((UIButton*)sender).selected;
((UIButton*)sender).selected = !((UIButton*)sender).selected;
CATransform3D transfrom = CATransform3DIdentity;
transfrom.m34 = -1.0/ 500;
if ( !isClicked ) {
transfrom = CATransform3DRotate(transfrom, degToRad(180.0), 1, 0, 0);
plane.transform = transfrom;
}
else
plane.transform = transfrom;
}
My question is how can do this flip with animation. I did try to use
UIView animateWithDuration(NSTimeInterval) delay:(NSTimeInterval) options:(UIViewAnimationOptions) animations:^(void)animations completion:(BOOL finished)completion
but still does not work. I'm not so sure that animation block is a good idea for this animation.
Do you guys have any ideas about this.
Use a CATransaction to change the implicit defaults
[CATransaction begin];
[CATransaction setAnimationDuration:1]; // in seconds
// Change layer animatable properties
plane.transform = transform;
[CATransaction commit];

enumeration values 'UIGestureRecognizerStatePossible', 'UIGestureRecognizerStateCancelled', and 'UIGestureRecognizerStateFailed' not handled in switch

When i using gesture functionality for flip up and down this is occurred kindly help to fix this error
- (void)doFlipForward:(UIGestureRecognizer *)aGestureRecognizer forOrientation:(UIInterfaceOrientation)anOrientation{
if(isAnimating)
return;
switch([aGestureRecognizer state])//There is occurred in this line
{
case UIGestureRecognizerStateBegan:
[CATransaction begin];
[CATransaction setDisableActions:YES];
[flipPage setHidden:NO];
[flipShadow setHidden:NO];
[CATransaction commit];
break;
case UIGestureRecognizerStateChanged:
{
CGFloat multiplier = 0.0f;
if(UIInterfaceOrientationIsPortrait(anOrientation))
{
multiplier = portraitMultiplierTable[(NSInteger)[aGestureRecognizer locationInView:self].x];
[thisPage setPortraitCurlAnimationPosition:multiplier];
[flipPage setPortraitCurlAnimationPosition:multiplier];
[flipShadow setPortraitCurlAnimationPosition:multiplier];
}
else
{
multiplier = landscapeMultiplierTable[(NSInteger)[aGestureRecognizer locationInView:self].x];
[thisPage setLandscapeCurlAnimationPosition:multiplier];
[flipPage setLandscapeCurlAnimationPosition:multiplier];
[flipShadow setLandscapeCurlAnimationPosition:multiplier];
}
}
break;
case UIGestureRecognizerStateEnded:
{
CGFloat transX = [(UIPanGestureRecognizer *)aGestureRecognizer translationInView:self].x;
CGFloat width = [self bounds].size.height * PAGE_RATIO;
if(width + transX < width/2)
{
[self animateOpen];
}
else
{
[self animateClose];
}
}
break;
}
}
You don't have a case for the listed values in the warning. Add the following to silence the warning:
default:
break;

Core animation animationDidStop with chained animations

I have two animations in a custom UIView, anim1 and anim2. Anim1 sets its delegate to self and there is an animationDidStop method in my class which triggers Anim2. If I want something else to occur when Anim2 finishes, how do I do this? Can I specify a delegate method with a different name?
UPDATE
I declare two animations as iVars:
CABasicAnimation *topFlip;
CABasicAnimation *bottomFlip;
I build each animation and set delgate to self e.g.
- (CABasicAnimation *)bottomCharFlap: (CALayer *)charLayer
{
bottomFlip = [CABasicAnimation animationWithKeyPath:#"transform"];
charLayer.transform = CATransform3DMakeRotation(DegreesToRadians(0), 1, 0, 0); //set to end pos before animation
bottomFlip.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(DegreesToRadians(-360), 1, 0, 0)];
bottomFlip.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(DegreesToRadians(-270), 1, 0, 0)];
bottomFlip.autoreverses = NO;
bottomFlip.duration = 0.5f;
bottomFlip.repeatCount = 1;
bottomFlip.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
bottomFlip.delegate = self;
bottomFlip.removedOnCompletion = FALSE;
return bottomFlip;
}
I then try and find bottomFlip in animationdidStop:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
if (theAnimation == bottomFlip) {
NSLog(#"Bottom Animation is: %#", bottomFlip);
}
NSLog(#"Animation %# stopped",theAnimation);
[bottomHalfCharLayerFront addAnimation:[self bottomCharFlap:bottomHalfCharLayerFront] forKey:#"bottomCharAnim"];
bottomHalfCharLayerFront.hidden = NO;
topHalfCharLayerFront.hidden = YES;
//insert the next one???
}
"Animation stopped" is logged but nothing else i.e. it doesn't seem to recognise the bottomFlip iVar
This seems to work:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
//NSLog(#"First Animation stopped");
if (anim ==[topHalfCharLayerFront animationForKey:#"topCharAnim"]) {
NSLog(#"Top Animation is: %#", anim);
topHalfCharLayerFront.hidden = YES;
[bottomHalfCharLayerFront addAnimation:[self bottomCharFlap:bottomHalfCharLayerFront] forKey:#"bottomCharAnim"];
bottomHalfCharLayerFront.hidden = NO;
}
else if ((anim ==[bottomHalfCharLayerFront animationForKey:#"bottomCharAnim"])) {
NSLog(#"Bottom Animation is: %#", anim);
}
Just hold onto a reference to your animations as ivars and compare their memory addresses to the one that gets handed off to animationDidStop:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
if (theAnimation == anim1)
{
// Spin off anim2
}
else
{
// anim2 stopped. Make something else occur
}
}