I want to approach the following scenario:
If I draw a rectangle using CGBezierPath and CAShapeLayer like the following:
CAShapeLayer *layerX = [CAShapeLayer layer];
layerX.path = path.CGPath;
layerX.lineWidth = 3;
layerX.strokeColor = [[UIColor whiteColor] CGColor];
layerX.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer: layerX];
And I want to add animation like the image I attached, this cleared area keeps moving around the rectangle path so it gives visual effect of the rectangle being drawn over and over. [Snake movement in old snake games]
I tried animating using CABasicAnimation but I literally couldn't achieve anything, thanks in advance.
Try this code may be helpful.
UIBezierPath *drawablePath = [UIBezierPath bezierPathWithRect:CGRectMake(100.0, 100.0, 150.0, 100.0)];
CAShapeLayer *squareLayer = [[CAShapeLayer alloc] init];
squareLayer.path = drawablePath.CGPath;
squareLayer.strokeColor = [UIColor redColor].CGColor;
squareLayer.lineWidth = 5.f;
squareLayer.fillColor = [UIColor clearColor].CGColor;
squareLayer.strokeEnd = 1.f;
//squareLayer.strokeEnd = 0.9; // this line use draw half line but some animation issue.
[self.view.layer addSublayer:squareLayer];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.fromValue = (id)[NSNumber numberWithFloat:0.10];
drawAnimation.toValue = (id)[NSNumber numberWithFloat:1.f];
drawAnimation.duration = 5.f;
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[squareLayer addAnimation:drawAnimation forKey:#"drawRectStroke"];
This code is use to draw rectangle with animation.
You have to animate both: strokeStart and strokeEnd and use a CAAnimationGroup to make them happen at the same time.
I found this link which does something similar but in swift. I think it might help.
I will try to implement it if you can't do it. But I can't right now.
Happy coding.
let drawablePath = UIBezierPath(rect: CGRect(x: 100, y: 100, width: 150, height: 150))
let squareLayer = CAShapeLayer()
squareLayer.path = drawablePath.cgPath;
squareLayer.strokeColor = UIColor.red.cgColor;
squareLayer.lineWidth = 5;
squareLayer.fillColor = UIColor.clear.cgColor;
squareLayer.strokeEnd = 1;
//squareLayer.strokeEnd = 0.9; // this line use draw half line but some animation issue.
flowerView.layer.addSublayer(squareLayer)
let drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.fromValue = 0.1
drawAnimation.toValue = 1.0
drawAnimation.duration = 5
drawAnimation.timingFunction = CAMediaTimingFunction(name:.linear)
squareLayer.add(drawAnimation, forKey:"drawRectStroke")
Not really an answer to the original question but here's a Swift 5 version of Dixit Akabari's answer, which was useful in itself in that it demonstrates how to animate drawing a UIBezierPath by animating strokeEnd.
Related
I'm trying to animate a chart so it scales from the center. Now it scales from left to right.
Initial State:
Final State:
Animation:
Here is my code:
maskLayer = [[CAShapeLayer alloc] init];
maskLayer.borderColor=[UIColor redColor].CGColor;
maskLayer.borderWidth=1;
maskLayer.fillColor=UIColorFromRGB(0x91E5FF).CGColor;
maskLayer.frame = CGRectMake(0,0, rect.size.width, rect.size.height);
maskLayer.path = finalPath.CGPath;
maskLayer.opacity=0.8;
[self.layer addSublayer:maskLayer];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"path"];
anim.duration = 4;
anim.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeBoth;
anim.fromValue = (id)[initialPath CGPath];
anim.toValue= (id)[finalPath CGPath];
[maskLayer addAnimation:anim forKey:nil];
To get the results you want, you can't just scale - you'll have the same path at the start and finish. Your stated example has a path animation.
All you have to do is draw your paths in relation to each other, the way you want them to animate. If you want to animate your path as if it's anchored in the center, you need to draw them like that. Center both your paths on each other.
Here's a simple example:
UIBezierPath* initialPath = [UIBezierPath bezierPathWithRect: CGRectMake(-25, -25, 50, 50)];
UIBezierPath* finalPath = [UIBezierPath bezierPathWithRect: CGRectMake(-40, -40, 80, 80)];
In this example, your square will scale up from the center. If your path is currently scaling from the right, it's because thats where your paths are aligned.
I have an abstract strange shaped UIView.
And I need to display a smooth appearance animation.
I assume I should apply that animation to the mask above that view.
The mask is going to be circle shaped.
So user will see the 1'2'3'4' sequence.
How can I implement that?
I suppose I should apply some affine transformation
to the mask view. In that case, what should be the original shape of the mask?
A vertical line?
A simple example,suppose I have a 100*100 imageview,
Note:to make it simple,I use hard code numbers
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.bounds = CGRectMake(0, 0, 100, 100);
maskLayer.position = CGPointMake(50, 50);
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.0, 50.0) radius:50 startAngle:0 endAngle:M_PI *2 clockwise:true];
maskLayer.path = path.CGPath;
maskLayer.fillColor = [UIColor clearColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.strokeEnd = 0.0;
maskLayer.lineWidth = 100;
self.imageview.layer.mask = maskLayer;
CABasicAnimation * animation = [CABasicAnimation animation];
animation.keyPath = #"strokeEnd";
animation.toValue = #(1.0);
animation.duration = 2.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[maskLayer addAnimation:animation forKey:#"keyFrame"];
I am drawing an animated circle using the UIBezierPath and CAShapeLayer. Now while adding the gradient effect the problem occurring is - CAGradientLayer adds it's gradient effect from upside down. But I want gradient effect from the starting point of the circle to the end point of the circle.
What I am getting is -
My code is - (it's a function)
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius+6,radius+6) radius:radius startAngle:DEGREES_TO_RADIANS(startAngle) endAngle:DEGREES_TO_RADIANS(endAngle) clockwise:isClockWise].CGPath;
//circle.position = CGPointMake(CGRectGetMidX(self.frame)-radius, CGRectGetMidY(self.frame)-radius);
circle.fillColor = fillColor.CGColor;
circle.strokeColor = strokeColor.CGColor;
circle.lineWidth = lineWidth;
circle.strokeEnd = percentToDraw/100;
circle.lineCap = kCALineCapRound;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = CGRectMake(0, 0, self.frame.size.width , self.frame.size.height);
NSMutableArray *colors = [NSMutableArray array];
[colors addObject:(id)[UIColor blackColor].CGColor];
[colors addObject:(id)strokeColor.CGColor];
gradientLayer.colors = colors;
[gradientLayer setMask:circle];
[self.layer addSublayer:gradientLayer];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = duration;
drawAnimation.repeatCount = 1.0;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:percentToDraw/100];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
How do I add gradient so that it starts(with red) and goes forward to finish point(with black). ?
Assuming you are talking about a conical/angle gradient, then there is no system provided method for achieving that with CAGradientLayer. There are libraries such as AngleGradientLayer that try to provide this functionality, however, as with any 3rd party solution, it should be used with caution.
You can also look at approaches like ones discussed here or here, however they would require you to slightly refactor your logic.
Note that this suggestions would only work in a circular case.
Trying to animatie an ellipse masked on a UIView to be scale transformed remaining in the center position.
I have found CALayer - CABasicAnimation not scaling around center/anchorPoint, and followed by adding a bounds property to the maskLayer CAShapeLayer however, this ends with the mask being positioned in the left corner with only 1/4 of it showing. I would like the mask to remain within the center of the screen.
#synthesize maskedView;
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Next"];
vc.view.frame = CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height);
vc.view.layer.bounds = self.view.layer.bounds;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, maskRect);
[maskLayer setPath:path];
CGPathRelease(path);
vc.view.layer.mask = maskLayer;
[self.view addSubview:vc.view];
maskedView = vc.view;
[self startAnimation];
}
Animation...
-(void)startAnimation
{
maskedView.layer.mask.bounds = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
maskedView.layer.mask.anchorPoint = CGPointMake(.5,.5);
maskedView.layer.mask.contentsGravity = #"center";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.duration = 1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = [NSNumber numberWithFloat:1.0f];
animation.toValue = [NSNumber numberWithFloat:5.0f];
[maskedView.layer.mask addAnimation:animation forKey:#"animateMask"];
}
Update
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
You can create a scale around the center of the object instead of (0, 0) like this:
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D tr = CATransform3DIdentity;
tr = CATransform3DTranslate(tr, self.bounds.size.width/2, self.bounds.size.height/2, 0);
tr = CATransform3DScale(tr, 3, 3, 1);
tr = CATransform3DTranslate(tr, -self.bounds.size.width/2, -self.bounds.size.height/2, 0);
scale.toValue = [NSValue valueWithCATransform3D:tr];
So we start out with the "identity" transform (which means 'no transform'). Then we translate (move) to the center of the object. Then we scale. Then we move back to origo so we're back where we started (we only wanted to affect the scale operation).
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
I am not sure why, but CAShapeLayer does not set the bounds property to the layer. In my case, that's was the problem.
Try setting the bounds. That should work.
I did something like this for building the initial circle
self.circle = [CAShapeLayer layer];
CGRect roundedRect = CGRectMake(0, 0, width, width);
self.circle.bounds = roundedRect;
UIBezierPath *start = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:width/2];
[self.circle setPath:start.CGPath];
self.circle.position = CGPointMake(whatever suits you);
self.circleView.layer.mask = self.circle;
[self.circleView.layer.mask setValue: #(1) forKeyPath: #"transform.scale"];
And something like this for scaling it
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scale.fromValue = [self.circleView.layer.mask valueForKeyPath:#"transform.scale"];
scale.toValue = #(100);
scale.duration = 1.0;
scale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.circleView.layer.mask setValue:scale.toValue forKeyPath:scale.keyPath];
[self.circleView.layer.mask addAnimation:scale forKey:scale.keyPath];
PD: AnchorPoint is by default (.5,.5) according to apple documentations...but we all know that does not mean anything right?
Hope it helps!!
I have written following function with many added tweaks and options, could be useful for many others.
func layerScaleAnimation(layer: CALayer, duration: CFTimeInterval, fromValue: CGFloat, toValue: CGFloat) {
let timing = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
let scaleAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timing)
scaleAnimation.duration = duration
scaleAnimation.fromValue = fromValue
scaleAnimation.toValue = toValue
layer.add(scaleAnimation, forKey: "scale")
CATransaction.commit()
}
Note: Don't forget to update size after animation completion, because CATransaction reverts back to actual size.
I am stuck at animating the uiLabel in my view.
Actually I just want to animate my label/Text on a circular path. I have searched a lot over internet but couldn't find any good tutorial or example that is doing something like my problem. I have done CAShapeLayer (circle) increasing from 0 to its maximum value....
Similarly I want that my label/text start moving from initial point in circular path and come back to its initial point after completing one circle.
Hope I have explained my problem quite precisely ... still if there is anything missing or non-understandable. please ask me.
Thanks in anticipation.
Hi iOmi i just google it and i found Best Question similar like your This
Bellow it's a jin's answer Hope its helps you
CAShapeLayer* circle = [[CAShapeLayer alloc] init];
CGMutablePathRef path = CGPathCreateMutable();
CGRect textRect = CGRectMake(self.view.bounds.size.width/4, (self.view.bounds.size.height-self.view.bounds.size.width/2)/2, self.view.bounds.size.width/2, self.bounds.size.width/2);
float midX = CGRectGetMidX(textRect);
float midY = CGRectGetMidY(textRect);
CGAffineTransform t = CGAffineTransformConcat(
CGAffineTransformConcat(
CGAffineTransformMakeTranslation(-midX, -midY),
CGAffineTransformMakeRotation(-1.57079633/0.99)),
CGAffineTransformMakeTranslation(midX, midY));
CGPathAddEllipseInRect(path, &t, textRect);
circle.path = path;
circle.frame = self.bounds;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = 60.0f;
[self.layer addSublayer:circle];
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 15.0f;
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"strokeEnd"];
[circle release];
UILabel* label = [[UILabel alloc] init];
label.text = #"Test Text";
label.font = [UIFont systemFontOfSize:20.0f];
label.center = CGPathGetCurrentPoint(path);
label.transform = CGAffineTransformMakeRotation(1.57079633);
[label sizeToFit];
[self.layer addSublayer:label.layer];
CAKeyframeAnimation* textAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
textAnimation.duration = 15.0f;
textAnimation.path = path;
textAnimation.rotationMode = kCAAnimationRotateAuto;
textAnimation.calculationMode = kCAAnimationCubicPaced;
textAnimation.removedOnCompletion = NO;
[label.layer addAnimation:textAnimation forKey:#"position"];
i just create a demo of it screen shot is:-
HERE IS DEMO LINK
http://www.sendspace.com/file/dq5i1e