Animating CAShapeLayer to follow the lline of UIBezierPath - ios

I an trying to animate the stroke of a UIBezierPath, as if drawing with a pencil.
However, I am finding that the layer seems to be creating an entire shape by connecting my first and last points on the curve -- and this is not what I want. I simply want a line of stroke 2.0 to animate. Here is my code:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20.0f, 20.f)];
[path addLineToPoint:CGPointMake(20.f, 200.f)];
CGFloat radius = 6.f;
[path addArcWithCenter:CGPointMake(20.f + radius, 200.f) radius:radius startAngle:M_PI endAngle:M_PI/2 clockwise:NO];
[path addLineToPoint:CGPointMake(240, 200.f+radius)];
[path addLineToPoint:CGPointMake(240.f, 480.f)];
[path stroke];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
shapeLayer.lineWidth = 2.0;
[self.scrollView.layer addSublayer:shapeLayer];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 4.f;
pathAnimation.fromValue = #(0.0f);
pathAnimation.toValue = #(1.0f);
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.6 :0.3 :0.8 :0.45];
[shapeLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
and here is what it looks like:

Related

Setting the lineWidth of a UIBezierPath not working

I'm trying to draw a circle but the circle border always the same thin no matter what value I set.
It feel like the line with always the default thinnest width.
- (void)drawCircleAtPoint:(CGPoint)center radius: (CGFloat)radius{
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineWidth = 4; // Here
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinRound;
CGFloat r = radius;
[path addArcWithCenter:center radius:r startAngle:0.0 endAngle:M_PI*2 clockwise:true];
[path stroke];
CAShapeLayer* layer = [CAShapeLayer new];
layer.path = path.CGPath;
layer.strokeColor = UIColor.redColor.CGColor;
layer.fillColor = UIColor.yellowColor.CGColor;
[layer addAnimation:[self getAnimation] forKey:nil];
[self.layer addSublayer:layer];
}
- (CABasicAnimation*)getAnimation {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = 1.2;
animation.fromValue = #0;
animation.toValue = #1;
return animation;
}
This really drove me crazy.
You need to change the width of CAShapeLayer and not UIBezierPath. Like this :
- (void)drawCircleAtPoint:(CGPoint)center radius: (CGFloat)radius{
CGFloat r = radius;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:center radius:r startAngle:0.0 endAngle:M_PI*2 clockwise:true];
[path setLineWidth:4]; // No need
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];
[path stroke];
CAShapeLayer *layer = [CAShapeLayer new];
layer.lineWidth = 4; // Add it here
layer.path = path.CGPath;
layer.strokeColor = UIColor.redColor.CGColor;
layer.fillColor = UIColor.yellowColor.CGColor;
[layer addAnimation:[self getAnimation] forKey:nil];
[self.view.layer addSublayer:layer];
}

How to draw trapezoid shape in iOS?

I need help to draw trapezoid shapes in iOS.
I am using the following code to draw the Pink shape, but it is not working:
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath addLineToPoint:CGPointMake(topView.frame.size.width, topView.frame.size.height/2)];
[trianglePath addLineToPoint:CGPointMake(100, topView.frame.size.height/2)];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,topView.frame.size.height-150, 200, 150)];
view.layer.mask = triangleMaskLayer;
[self.view addSubview:view];
How can I draw the pink-colored shape?
-(void)TriangularPinkView{
UIBezierPath *path = [UIBezierPath bezierPath];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
shapeLayer.lineWidth = 0.5;
CGPoint start = CGPointMake(CGRectGetMinX(self.pinkView.frame), CGRectGetMidY(self.pinkView.frame)+CGRectGetHeight(self.pinkView.frame)*.25);
[path moveToPoint:start];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.pinkView.frame), CGRectGetMidY(self.pinkView.frame))];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.pinkView.frame), CGRectGetMaxY(self.pinkView.frame))];
[path addLineToPoint:CGPointMake(CGRectGetMinX(self.pinkView.frame), CGRectGetMaxY(self.pinkView.frame))];
shapeLayer.path = [path CGPath];
// shapeLayer.fillRule = kCAFillRuleEvenOdd;
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[self.view.layer addSublayer:shapeLayer];
}

How CAShapelayer transform from circle to square (or vice versa) with animation?

How CAShapelayer transform from circle to square (or vice versa) with CABasicAnimation?
After doing some work around, i have ended with this
First make global object of UIBezierPath for square and circle path and also for CAShapeLayer object.
#implementation Demo{
CAShapeLayer *shapeLayer;
UIBezierPath *sqPath;
UIBezierPath *crclePath;
}
Now, customize your shapeLayer and add to layer like below in viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
shapeLayer.lineWidth = 2;
shapeLayer.lineJoin = kCALineCapRound;
sqPath = [self makeSquare:self.view.center squareWidth:200];
shapeLayer.path = [sqPath CGPath];
crclePath = [self makeCircle:self.view.center radius:100];
[self.view.layer addSublayer:shapeLayer];
}
Below is method that will make perfect square
-(UIBezierPath*)makeSquare:(CGPoint)centrePoint squareWidth:(float)gain{
float x=centrePoint.x-(gain/2);
float y=centrePoint.y-(gain/2);
UIBezierPath *apath = [UIBezierPath bezierPath];
[apath moveToPoint:CGPointMake(x,y)];
[apath addLineToPoint:apath.currentPoint];
[apath addLineToPoint:CGPointMake(x+gain,y)];
[apath addLineToPoint:apath.currentPoint];
[apath addLineToPoint:CGPointMake(x+gain,y+gain)];
[apath addLineToPoint:apath.currentPoint];
[apath addLineToPoint:CGPointMake(x,y+gain)];
[apath addLineToPoint:apath.currentPoint];
[apath closePath];
return apath;
}
Below is method helps to make circle
- (UIBezierPath *)makeCircle:(CGPoint)center radius:(CGFloat)radius
{
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:center radius:radius startAngle:-M_PI endAngle:-M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:-M_PI/2 endAngle:0 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:-M_PI clockwise:YES];
[circlePath closePath];
return circlePath;
}
Finally, animation done in action of btnDone which use CABasicAnimation to animate transformation from square to circle or vice versa.
- (IBAction)btnDone:(id)sender {
btnDowrk.selected=!btnDowrk.selected;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
if (btnDowrk.selected) {
animation.fromValue = (__bridge id)(sqPath.CGPath);
animation.toValue = (__bridge id)(crclePath.CGPath);
shapeLayer.path = crclePath.CGPath;
}
else{
animation.fromValue = (__bridge id)(crclePath.CGPath);
animation.toValue = (__bridge id)(sqPath.CGPath);
shapeLayer.path = sqPath.CGPath;
}
[shapeLayer addAnimation:animation forKey:#"animatePath"];
}
You can play with cornerRadius. For example,
UIView *testView = [[UIView alloc]initWithFrame:CGRectMake(50, 50, 50, 50)];
CAShapeLayer *layer = [[CAShapeLayer alloc]initWithLayer:testView.layer]; // this is square layer
layer.cornerRadius = 25.0; // this is round layer
layer.cornerRadius = 0.0; // this is again square layer
You can switch views directly from round to square and square to round like above approach!
Here is create the circle path:
- (UIBezierPath *)circlePathWithCenter:(CGPoint)center radius:(CGFloat)radius
{
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:3*M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:3*M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath closePath];
return circlePath;
}
Here is the square path:
- (UIBezierPath *)squarePathWithCenter:(CGPoint)center size:(CGFloat)size
{
CGFloat startX = center.x-size/2;
CGFloat startY = center.y-size/2;
UIBezierPath *squarePath = [UIBezierPath bezierPath];
[squarePath moveToPoint:CGPointMake(startX, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY+size)];
[squarePath addLineToPoint:CGPointMake(startX, startY+size)];
[squarePath closePath];
return squarePath;
}
The animation Part
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (__bridge id)(self.stateLayer.path);
animation.toValue = (__bridge id)(self.stopPath.CGPath);
self.stateLayer.path = self.stopPath.CGPath;
[self.stateLayer addAnimation:animation forKey:#"animatePath"];

Animation for Layer in iOS

I masked an image with BezierPath.
I want to animate that mask image.
My code is as follow:
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(84.95, 205.11)];
[bezierPath addCurveToPoint: CGPointMake(122.89, 232.14) controlPoint1: CGPointMake(84.95, 205.11) controlPoint2: CGPointMake(110.81, 223.56)];
[bezierPath addCurveToPoint: CGPointMake(124.33, 233.04) controlPoint1: CGPointMake(123.37, 232.46) controlPoint2: CGPointMake(123.85, 232.78)];
[bezierPath closePath];
CAShapeLayer *maskImage = [CAShapeLayer layer];
maskImage.path = bezierPath.CGPath;
maskImage.fillColor = [[UIColor blackColor] CGColor];
_myImageView.layer.mask = maskImage;
CAShapeLayer *border = [CAShapeLayer layer];
border.path = bezierPath.CGPath;
border.strokeColor = [UIColor redColor].CGColor;
border.fillColor = [[UIColor clearColor] CGColor];
border.lineWidth = 5;
[_myImageView.layer addSublayer:border];
How can I animate this?
Thank you
Use following piece of code:
#property (nonatomic, retain) CAShapeLayer *animationLayer;
- (IBAction)drawPattern:(id)sender{
[self setup];
[self.animationLayer removeAllAnimations];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 10.0;
pathAnimation.fromValue = #(0);
pathAnimation.toValue = #(1);
[self.animationLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
- (void)setup{
[self.animationLayer removeFromSuperlayer];
self.animationLayer = nil;
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(84.95, 205.11)];
[bezierPath addCurveToPoint: CGPointMake(122.89, 232.14) controlPoint1: CGPointMake(84.95, 205.11) controlPoint2: CGPointMake(110.81, 223.56)];
[bezierPath addCurveToPoint: CGPointMake(124.33, 233.04) controlPoint1: CGPointMake(123.37, 232.46) controlPoint2: CGPointMake(123.85, 232.78)];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
// provide frame & bounds so that path can be drawn over that.
pathLayer.frame = CGRectMake(35, 100, 250, 200);
pathLayer.bounds = CGRectMake(35, 100, 250, 200);
pathLayer.path = bezierPath.CGPath;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.fillColor = [[UIColor clearColor] CGColor];
pathLayer.lineWidth = 6.f;
pathLayer.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:pathLayer];
[self setAnimationLayer:pathLayer];
}
Please let me know if this works for you & also if anything else comes up.
At the end of your code try below one
CAShapeLayer *pathLayer;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [aPath CGPath];
shapeLayer.strokeColor = [[UIColor greenColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 5.0f;
shapeLayer.lineJoin = kCALineJoinBevel;
[myImageView.layer addSublayer:shapeLayer];
pathLayer = shapeLayer;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 3.0;
pathAnimation.fromValue = #(0.0f);
pathAnimation.toValue = #(1.0f);
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
We can animate the image with path using CABasicAnimation.
CABasicAnimation has position,path,transform,opacity,shadow......which including all layers keypaths,shape layer keypaths,CA layer keypaths,Text layer keypaths,Mask layer keypaths,UIView layer keypaths,Effect,Emit and replicator keypaths.
According to your question for drawing path we can use path and strokeEnd.But most prepare strokeEnd for accessing shape style properties.
So I think strokeEnd is the best one for animating the mask image with bezier path.
Below I give the code for path and strokeEnd also I diffrecicate both.
If I use animationWithKeyPath is path
The shape to be rendered and animatable.It is specifying the shape path.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.fromValue = #(0);
animation.toValue = #(1);
animation.duration = 6.0f;
[maskImage addAnimation:animation forKey:#"animatePath"];
The Path is a collection of individual components like lines and arcs that can be drawn and also animated.It is fundamental concept of Quartz 2D.
In below code I use strokeEnd
Generally if we want to access the shape style properties we can use
strokeEnd
The relative location at which to stop stroking the path and animatable.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.fromValue = #(0);
animation.toValue = #(1);
animation.duration = 6.0f;
[maskImage addAnimation:animation forKey:#"strokeEnd"];
The value of this property must be in the range 0.0 to 1.0. The default value of this property is 1.0.
Also strokeEnd defines following thing
Combined with the strokeStart property, this property defines the subregion of the path to stroke. The value in this property indicates the relative point along the path at which to finish stroking while the strokeStart property defines the starting point.

How to create a circular dotted line as a CALayer?

I read this post Draw dotted (not dashed!) line, with IBDesignable in 2017 about drawing a dotted line (rather than a dashed line). However, I am not too familiar with graphics generally and I'm wondering how I can do this with a CALayer (so I don't have to do the whole get current graphics context thing).
I am trying to produce a dotted line that looks like this (the white part, ignore the background):
Here's the code I have working to produce a dotted line:
CAShapeLayer *shapelayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addLineToPoint:endPoint];
[path setLineCapStyle:kCGLineCapRound];
UIColor *fill = [UIColor whiteColor];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = fill.CGColor;
shapelayer.lineWidth = 4.0;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:4],[NSNumber numberWithInt:6 ], nil];
shapelayer.path = path.CGPath;
return shapelayer;
How can I mirror the code in the SO post I referenced but continue using a CALayer?
I tried modifying the code from that post like so:
UIBezierPath * path = [[UIBezierPath alloc] init];
[path moveToPoint:startPoint];
[path addLineToPoint:endPoint];
[path setLineWidth:8.0];
CGFloat dashes[] = { path.lineWidth, path.lineWidth * 2 };
[path setLineDash:dashes count:2 phase:0];
[path setLineCapStyle:kCGLineCapRound];
[path stroke];
CAShapeLayer *returnLayer = [CAShapeLayer layer];
returnLayer.path = path.CGPath;
return returnLayer;
However, this ends up drawing nothing.
I hope this will help you.
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 100, 100)] CGPath]];
circleLayer.lineWidth = 2.0;
[circleLayer setStrokeColor:[[UIColor redColor] CGColor]];
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
circleLayer.lineJoin = kCALineJoinRound;
circleLayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:2],[NSNumber numberWithInt:3 ], nil];
// self.bgImage is parentView
[[self.bgImage layer] addSublayer:circleLayer];
self.bgImage.layer.borderWidth = 1.0f;
self.bgImage.layer.borderColor = [[UIColor blueColor]CGColor];
I have attached the output of the image
In my case I had to set the lineCapStyle rather than the lineJoinStyle.

Resources