I have a few rects that I draw a circle in with UIBezierPath and CAShapeLayer. They should act like buttons, in which when they are tapped they are being selected and when a different button is tapped they are being deselected, where each of these states should change button appearance between these two conditions:
deselected: selected:
This is the general code (which is called when selection changes):
CGFloat radius = isSelected ? 9.8 : 13. ;
CGFloat strokeWidth = isSelected ? 10.5 : 2;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
[bezierPath addArcWithCenter:center radius:radius startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
[progressLayer setStrokeColor:[UIColor redColor].CGColor];
[progressLayer setLineWidth:strokeWidth]];
[self.layer addSublayer:progressLayer];
When this rect is selected, I would like to change the strokeWidth of the circle only inward, and keep the original radius. When it's deselected, the opposite should happen.
As you can see I change both the radius and the lineWidth, because when I make strokeWidth bigger, the outer radius is growing as well. And as I said, I want to keep it the same.
The problem is that I want it animated in a way that the width grows inward when selected, and out when deselected, without changing the outer radius at all.
What I have so far, is an animation of the change of strokeWidth together with the radius in order to keep the result with the same appeared radius. But it's not what I need. As you can see, only the radius change is animated here, and the result is a growing circle, which I don't want.
This is the code of the animation (in addition to the original code):
CABasicAnimation *changeCircle = [CABasicAnimation animationWithKeyPath:#"path"];
changeCircle.duration = 0.6;
changeCircle.fromValue = (__bridge id)oldPath; // current bezier path
changeCircle.toValue = (__bridge id)newPath; // new bezier path
[progressLayer addAnimation:changeCircle forKey:nil];
I had also the opposite code, that animates only the change of strokeWidth. It also doesn't do what I need.
Is there a way to the strokeWidth grow or get small without changing the outer radius?
Like this:
Thanks to #Chris, I managed to work it out.
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnimation.fromValue = (__bridge id) oldPath;
pathAnimation.toValue = (__bridge id) newPath;
CABasicAnimation *lineWidthAnimation = [CABasicAnimation animationWithKeyPath:#"lineWidth"];
lineWidthAnimation.fromValue = isSelected ? #2. : #10.5;
lineWidthAnimation.toValue = isSelected ? #10.5 : #2.;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.3;
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.animations = #[pathAnimation, lineWidthAnimation];
CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
[progressLayer setStrokeColor:self.color.CGColor];
[progressLayer setLineWidth:isSelected ? 10.5 : 2];
[progressLayer setFillColor:[UIColor clearColor].CGColor];
[progressLayer addAnimation:group forKey:#"selectedAnimations"];
[self.layer addSublayer:progressLayer];
Related
This code creating a complete circle but I want to create a circle based on value either 360 or percentage 100%. IF percentage is 56% I want 56% circle. Like this based on percentage/value, I want circle.
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(29, 29) radius:27 startAngle:-M_PI_2 endAngle:2 * M_PI - M_PI_2 clockwise:YES].CGPath;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor greenColor].CGColor;
circle.lineWidth = 4;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 10;
animation.removedOnCompletion = NO;
animation.fromValue = #(0);
animation.toValue = #(1);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[_colouredCircle.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[_colouredCircle.layer addSublayer:circle];
That means that your toValue should match your percentage.
In your case, animation.toValue = #(0.56); would end the stroke at 56% of the circle (0...1 range).
See this answer for information about keeping the final animated value after completion.
TLDR: You have to also update the model layer.
circle.strokeStart = 0;
circle.strokeEnd = 0.56; // Your target value
...
// Your animation
Check out this circular progress bar and set colour as you want to set at particular position.
I am using below code to draw a Pie slice layer with CAShapeLayer.
CGFloat endAngle = (M_PI*2/5)-M_PI_2;
CAShapeLayer *outerBorderCircle = [CAShapeLayer layer];
int outerradius = self.frame.size.width/2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(outerradius, outerradius)];
[path addLineToPoint:CGPointMake(outerradius, self.outerCircleBorder)];
[path addArcWithCenter:CGPointMake(outerradius,outerradius) radius:outerradius-self.outerCircleBorder startAngle:-M_PI_2 endAngle:endAngle clockwise:YES];
[path closePath];
outerBorderCircle.path = path.CGPath;
outerBorderCircle.fillColor = [UIColor whiteColor].CGColor;
outerBorderCircle.strokeColor = self.borderShadowColor.CGColor;
outerBorderCircle.lineWidth = self.outerCircleBorder;
outerBorderCircle.lineJoin = kCALineJoinRound;
outerBorderCircle.zPosition = MAXFLOAT;
CABasicAnimation *startanimation = [CABasicAnimation animationWithKeyPath:#"fillColor"];
startanimation.removedOnCompletion = YES;
startanimation.fromValue = (id)[[UIColor clearColor] CGColor];
startanimation.toValue = (id)[[UIColor whiteColor] CGColor];
startanimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
startanimation.duration = 10.0;
[startanimation setFillMode:kCAFillModeBoth];
[outerBorderCircle addAnimation:startanimation forKey:#"fillColor"];
[self.layer addSublayer:outerBorderCircle];
But animation part working as fade in fade out. But i need to animate arc kind of animation.
Please help me.
To animate the stroke you should animate those properties:
-strokeStart
-strokeEnd
From the documentation:
Combined with the strokeEnd 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 begin stroking while the
strokeEnd property defines the end point. A value of 0.0 represents
the beginning of the path while a value of 1.0 represents the end of
the path. Values in between are interpreted linearly along the path
length.
[UPDATE]
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
Credits to Rob Mayoff
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"];
there are a lot of examples how to animate along a UIBezierPath. But i can't figure it out, how to animate only the appendPath of an UIBezierPath.
In my case i have the following example:
// Create path from view controller
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:
CGRectMake(0, 0, tutorialController.bounds.size.width,
tutorialController.bounds.size.height) cornerRadius:0];
// Create circle path
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(x, y, 2.0*radius, 2.0*radius) cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
// Create spot layer
spotLayer = [CAShapeLayer layer];
spotLayer.path = path.CGPath;
spotLayer.fillRule = kCAFillRuleEvenOdd;
spotLayer.fillColor = [UIColor blackColor].CGColor;
spotLayer.opacity = 0.90;
So basically its a circle inside a rect and i now want to animate only the circle. In all attempts the rect was also moved.
Here is my example, how i can animate "along" a path
CGPoint pathStart = CGPointMake(96.000000, 226.000000);
CGPoint pathEnd = CGPointMake(120.000000, 300.000000);
[circlePath moveToPoint:pathStart];
[circlePath addLineToPoint:pathEnd];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = circlePath.CGPath;
anim.duration = 5.0;
anim.calculationMode = kCAAnimationPaced;
[spotLayer addAnimation:anim forKey:#"bezierPathAnim"];
But that is not what i want...
What i want to do is, to move my circle path (which is an appended path of a rectangle path) via animation
Does anybody has some ideas?
Regards
I solved my Problem :-)
Here is the solution...
// If there is an old path animate to the new path
if(oldPath != nil){
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"path"];
[anim setFromValue:(__bridge id)oldPath];
[anim setToValue:(__bridge id)[path CGPath]];
[anim setDuration:0.3];
[anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[spotLayer addAnimation:anim forKey:#"path"];
}
First of all i kept the oldPath (starting point) and then animate to the new setted path.