I am using CAShapeLayer to generate some shapes. Actually I have 4 CAShapeLayers that represent some triangles in the screen. I create the shapes using CGMutablePathRef and setting the path property of each CAShapeLayer. Triangles are in different shapelayers since I have to animate each triangle in a different way.
I perform some animations on my viewcontroller using CABasicAnimation and animating the path property of the CAShapeLayer, something like:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#”path”];
animation.duration = 2.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 1e100f;
animation.autoreverses = YES;
animation.fromValue = (id)initialPath;
animation.toValue = (id)newPath;
[shapeLayer addAnimation:animation forKey:#”animatePath”];
That's ok, I can draw shapes and animate them. My problem is that this blends the images using the default alpha blending (result = (alpha * foreground) + (1 - alpha) * background). What I want is to use multiply blending (kCGBlendModeMultiply), but I don't know how to mix CAShapeLayer drawing and blending modes. I've successfully drawn my shapes creating a custom UIView and overriding the drawRect method, but then I can't animate the shapes.
Has anyone had any success changing the color blending of a CAShapeLayer?
Related
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 2.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
Instead of the path, how can I set animation to last line ?
You cannot just animate "the last line in a path". But you can achieve the visual effect by using two entirely separate shape layers, one with the path thus far that is not to be animated (the blue path in my example below) and a completely separate layer whose path is just the last the segment of the line, which will be animated (a red path in my example). You can then:
Make the path of the blue shape layer to be the just the existing, unanimated portion of the final path.
Make the path for the red shape layer to be just the last segment of the new path;
Animate the red layer's strokeEnd from #(0.0) to #(1.0);
When that's done (e.g. in animationDidStop:finished:), remove that red shape layer's path altogether and extend the path of the blue shape layer's path; and
Repeat as needed.
When you do this, it will look like you animated another segment onto the blue shape layer, but what you really did is that you create a separate layer with just the portion to be animated, animate that, and when done, remove that layer and update the original layer's path accordingly
Note, in the above animated example, the red path and the blue paths are on different shape layers, but it almost feels like it's one path because one layer's path starts right where the other one left off. Clearly, in your final product, you would make the two shape layers appear the same, which would be less jarring than the red/blue combination above. I just used different colors here so you could understand what the two different shape layers were doing.
FYI, this is the code that generated the above drawing:
- (void)addLineToPoint:(CGPoint)point {
// create a path that consists just of the portion to be animated
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.lastPoint];
[path addLineToPoint:point];
// set the redAnimatedPathLayer's path to be this short segment
self.redAnimatedPathLayer.path = [path CGPath];
// and animate it
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.redAnimatedPathLayer.path = [path CGPath];
pathAnimation.duration = 0.5;
pathAnimation.fromValue = #(0.0);
pathAnimation.toValue = #(1.0);
pathAnimation.delegate = self;
[self.redAnimatedPathLayer addAnimation:pathAnimation forKey:#"strokeEndKey"];
// save the point for future reference
self.lastPoint = point;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
// update the blue layer's path to include this new point
[self.mainPath addLineToPoint:self.lastPoint];
self.blueNonAnimatedLayer.path = self.mainPath.CGPath;
// reset the red layer's path
self.redAnimatedPathLayer.path = nil;
[self repeat];
}
I am working on a kids letter tracing app - see attached screen shot.
I was able to display the letter by using the bezierpath identified by the font glyphs and allow writing the touches inside the bezierpath.
Now, i want to add an help animation so that it shows how to write this letter from start to finish.
How to do that?
Consider we display "A" and i want to show animation shows how to write "A" for kids.
Any pointers / ideas.
thanks much.
I used this animation for my app. Created a circular progress bar that fills up dot clockwise
CABasicAnimation *pathAnim = [CABasicAnimation animationWithKeyPath: #"path"];
pathAnim.toValue = (id)newPath.CGPath;
// -- initialize CAAnimation group with duration of 0.2secs and beginTime will begin after another.
CAAnimationGroup *anims = [CAAnimationGroup animation];
anims.animations = [NSArray arrayWithObjects:pathAnim, nil];
anims.removedOnCompletion = NO;
anims.duration = 0.2f;
anims.beginTime = CACurrentMediaTime() + anims.duration*i;
anims.fillMode = kCAFillModeForwards;
[anims setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[progressLayer addAnimation:anims forKey:nil];
There is no straightforward way to do this. I imagine the following would be easiest to implement:
Create a path per character that will trace how the stroke will be written. If you have limited number of characters like in the alphabet, it's possible to do this by hand. You can implement something like this to capture the path: http://code.tutsplus.com/tutorials/smooth-freehand-drawing-on-ios--mobile-13164
Use your current path (in red in your photo) as a clipping mask so the fill does not overflow.
Stroke the path you created in 1. with a very thick stroke to do the fill.
I can't find a "built-in" way to animate the fill as you want. There is a built-in way to animate the stroke, however:
Use a UIView with a CAShapeLayer backing it. CAShapeLayer has a path property. You can then apply an animation to the layer's strokeStart and/or strokeEnd properties.
Hope it helps.
After loading your bezierpath add init the animation:
CAKeyframeAnimation *shapeKeyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"strokeEnd"];
shapeKeyframeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
shapeKeyframeAnimation.keyTimes = #[#0.0f, #0.6f, #1.0f];
shapeKeyframeAnimation.values = #[#0.0f, #1.0f, #1.0f];
shapeKeyframeAnimation.duration = 4.0f;
shapeKeyframeAnimation.repeatCount = CGFLOAT_MAX;
then add it to your shape layer:
[shapeLayer addAnimation:_loadingKeyframeAnimation forKey:#"stoke"];
I'm using CAKeyframeAnimation to move CALayer on a circle trajectory. Sometimes I need to stop animation and to move animation to the point at which the animation stopped. Here the code:
CAKeyframeAnimation* circlePathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
CGMutablePathRef circularPath = the circle path;
circlePathAnimation.path = circularPath;
circlePathAnimation.delegate = self;
circlePathAnimation.duration = 3.0f;
circlePathAnimation.repeatCount = 1;
circlePathAnimation.fillMode = kCAFillModeForwards;
circlePathAnimation.removedOnCompletion = NO;
circlePathAnimation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:.43 :.78 :.69 :.99];
[circlePathAnimation setValue:layer forKey:#"animationLayer"];
[layer addAnimation:circlePathAnimation forKey:#"Rotation"];
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
CALayer *layer = [anim valueForKey:#"animationLayer"];
if (layer) {
CALayer *presentationLayer = layer.presentationLayer;
layer.position = presentationLayer.position;
}
}
But the presentation layer has no changes in position!!! I read that it is no longer reliable from ios 8. So is there any other way I can find the current position of animated layer?
I wasn't aware that the position property of the presentation layer stopped telling you the location of your layer in iOS 8. That's interesting. Hit testing using the hitTest method on the presentation layer still works, I just tried it on an app I wrote a while back.
Pausing and resuming an animation also still works.
For a simple path like a circle path you could check the time of the animation and then calculate the position using trig.
For more complex paths you'd have to know about the path your layer was traversing. Plotting a single cubic or quadratic bezier curve over time is pretty straightforward, but a complex UIBezierPath/CGPath with a mixture of different shapes in it would be a bear to figure out.
I am trying to achieve the filling of a circle(something like a pie).
I want to fill it from 0 to 360
This is what i have done
CAShapeLayer *circleLayer=[CAShapeLayer layer];
circleLayer.path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:100 startAngle:0 endAngle:360 clockwise:YES].CGPath;
circleLayer.strokeColor=[UIColor redColor].CGColor;
circleLayer.fillColor=[UIColor blueColor].CGColor;
[self.view.layer addSublayer:circleLayer];
//adding animation
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:#"fillColor"];
basicAnimation.fillMode=kCAFillModeForwards;
basicAnimation.fromValue=(id)[UIColor clearColor].CGColor;
basicAnimation.toValue=(id)[UIColor yellowColor].CGColor;
basicAnimation.duration=1.5f;
basicAnimation.repeatCount=10;
basicAnimation.autoreverses=YES;
[circleLayer addAnimation:basicAnimation forKey:#"fillColor"];
I am not able to figure out,how do i acheive the effect of color filling from start angle to end angle.
Thanks
Your description of what you want to do is unclear. Are you trying to create a "clock wipe" effect, where the color change sweeps like the second hand of a clock?
If so, check out this demo app I created on github:
github animation demo project
Look in particular at the description of the Clock Wipe animation in the read me file.
Here is what the clock wipe animation looks like:
This project animates the mask of an image view to do a clock wipe reveal animation, but it would be a simple matter to animate a shape layer as a content layer of a view instead of making it a mask.
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 0.7f;
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circleLayer addAnimation:animation forKey:#"strokeEnd"];
I have implemented a shape layer subclass for similar requirements. Here is the Github link. For your requirement the arc start radius is 0.0f and end radius is the circle radius. You can also refer the sample project in that link.
I am trying to make an effect where at first the entire screen is masked out. As a ball moves across the screen, the ball unmasks the area that it is in AND the areas that it WAS in remain unmasked.
I have the following code:
CALayer * ball = [CALayer layer];
ball.bounds = CGRectMake(0, 0, 42, 42);
ball.position = [[[alphabet controls] objectAtIndex:0] CGPointValue];
ball.contents = (id)([UIImage imageNamed:#"done.png"].CGImage);
[self.layer addSublayer:ball];
[self.layer setMask:ball];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = path;
anim.repeatCount = HUGE_VALF;
anim.duration = 8.0;
[ball addAnimation:anim forKey:#"race"];
This animation masks the entire view and shows only what is behind the ball layer.
My question is : How can I keep unmasked the parts of the screen that were revealed?
Hmmm.
What you want is an image that contains all the pixels that the ball shape traveled through.
If you were animating the ball with frame-based animation, you could create a grayscale (or 1-bit) image and install that as the content of your mask layer. Then, as you moved the ball, you could draw it into your mask image with each frame.
I'm not sure how to get the same effect with Core animation.
You could make your mask a CAShapeLayer, create a CGPath that describes the entire path of your ball, and make that the path for your shape layer. If your ball is round, you could set the line thickness of the shape layer to your ball size. That would work. If you ball is an irregular shape, though, that approach wouldn't work. Quartz graphics on iOS doesn't have any way I know of to stroke a path with an arbitrary-shaped brush.