Revealing a UIImage with a radial animation - ios

I have a UIImageView that i want to slowly reveal in a radial clockwise fashion.
Here is an example of how i want to reveal the image over the course of 1 second. Im basically trying to simulate a marker drawing a circle around something.

Here's another variation of the animating a UIBezierPath. Bottom line, come up with a UIBezierPath and then just animate the drawing of it with a CABasicAnimation of the strokeEnd:
- (void)drawBezier
{
CGPoint startPoint = CGPointMake(self.view.frame.size.width / 2.0, 50);
UIBezierPath *path = [self strokePath:startPoint];
CAShapeLayer *oval = [[CAShapeLayer alloc] init];
oval.path = path.CGPath;
oval.strokeColor = [UIColor redColor].CGColor;
oval.fillColor = [UIColor clearColor].CGColor;
oval.lineWidth = 5.0;
oval.strokeStart = 0.0;
oval.strokeEnd = 1.0;
[self.view.layer addSublayer:oval];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
anim.duration = 1.0;
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:1.0f];
[oval addAnimation:anim forKey:#"strokeEndAnimation"];
}
You obviously have to create your bezier. This is my complicated little algorithm (it's an array of five points, the angle at each point, and the weight of the anchor points for each), but I'm sure you can come up with something simpler:
- (UIBezierPath *)strokePath:(CGPoint)startPoint
{
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(startPoint.x, startPoint.y)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x + 100.0, startPoint.y + 70.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x, startPoint.y + 140.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x - 100.0, startPoint.y + 70.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x + 10.0, startPoint.y + 10)],
nil];
NSArray *angles = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:M_PI_2 * 0.05],
[NSNumber numberWithFloat:M_PI_2],
[NSNumber numberWithFloat:M_PI],
[NSNumber numberWithFloat:M_PI_2 * 3],
[NSNumber numberWithFloat:0],
nil];
NSArray *weight = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:100.0 / 1.7],
[NSNumber numberWithFloat: 70.0 / 1.7],
[NSNumber numberWithFloat:100.0 / 1.7],
[NSNumber numberWithFloat: 70.0 / 1.7],
[NSNumber numberWithFloat:100.0 / 1.7],
nil];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
for (NSInteger i = 1; i < 5; i++)
{
CGPoint pointPrevious = [[points objectAtIndex:(i-1)] CGPointValue];
CGPoint pointCurrent = [[points objectAtIndex:i] CGPointValue];
CGFloat anglePrevious = [[angles objectAtIndex:(i-1)] floatValue];
CGFloat angleCurrent = [[angles objectAtIndex:i] floatValue];
CGFloat weightPrevious = [[weight objectAtIndex:(i-1)] floatValue];
CGFloat weightCurrent = [[weight objectAtIndex:i] floatValue];
[path addCurveToPoint:pointCurrent
controlPoint1:CGPointMake(pointPrevious.x + cosf(anglePrevious) * weightPrevious,
pointPrevious.y + sinf(anglePrevious) * weightPrevious)
controlPoint2:CGPointMake(pointCurrent.x - cosf(angleCurrent) * weightCurrent,
pointCurrent.y - sinf(angleCurrent) * weightCurrent)];
}
return path;
}
And if this isn't acceptable (because it's a solid path rather than your artistic rendering) here's a trick that works (but is a little complicated, so bear with me). You can put your image on the layer, put the UIBezierPath oval on top of it and rather than animate the drawing of the bezier path in red, draw it in white so it obscures your image and blends into the background and then reverse the animation of the drawing of the UIBezierPath. That way, it starts with your image with a white UIBezierPath drawn on top of it, completely obscuring your image, and it animates the un-drawing of the UIBezierPath, revealing your image underneath. But anyway, this is how you add the image to a layer and how you "undraw" the bezier.
CAShapeLayer *imagelayer = [[CAShapeLayer alloc] init];
UIImage *image = [UIImage imageNamed:#"oval.png"];
imagelayer.contents = (id)image.CGImage;
imagelayer.frame = CGRectMake(startPoint.x - image.size.width / 2.0, startPoint.y, image.size.width, image.size.height);
[self.view.layer addSublayer:imagelayer];
CAShapeLayer *oval = [[CAShapeLayer alloc] init];
oval.path = path.CGPath;
oval.strokeColor = [UIColor whiteColor].CGColor;
oval.fillColor = [UIColor clearColor].CGColor;
oval.lineWidth = 80.0;
oval.strokeStart = 0.0;
oval.strokeEnd = 0.0;
[self.view.layer addSublayer:oval];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
anim.duration = 1.0;
anim.fromValue = [NSNumber numberWithFloat:1.0f];
anim.toValue = [NSNumber numberWithFloat:0.0f];
[oval addAnimation:anim forKey:#"strokeEndAnimation"];
Clearly, some effort will have to be put into coming up with a UIBezierPath that directly covers up your image, etc., but it can be done. (That exercise, by the way, would be greatly simplified if you (a) drew the UIBezier, (b) take a screen snapshot, (c) throw it into photoshop to lend it the appearance you want, and then (d) use that for the underlying image.) Anyway, for my crude attempt below, I just made the thickness of the bezier incredibly thick, so I could be pretty sloppy about it, but you will want to be more careful (because I assume you're drawing your circle around something and you can't have the UIBezierPath overlay obscuring that).
Alternatively, if you want to use CodaFi's first suggestion, but don't have the individual images created yet, you can stick with this super-thick bezier path, but you could use a permutation of the code above to create your images (e.g. start with strokeStart at zero, don't both animating, but rather take a screen snapshot, do it again and again, incrementing the strokeStart by 1/30th (or whatever) each time, and taking another screen snapshot, etc.).
Hopefully someone can come up with a more elegant solution!

Related

Circle strokeWidth change animation without changing the circle radius

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];

CAGradientLayer issues for animated circle created using CAShapeLayer

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.

Draw circle animation with rounded stroke in iOS

I'm trying to animate a circle being drawn in iOS 7 - which is quite easy.
My problem is that I need the stroke to have rounded ends. The way I'm trying to do that now is by adding another circle to the start position of the animation.
Then for the end that is moving, I need another circle to follow along. It's doing pretty much what I want, but I need it to use an easeInOutQuart timing which is turning out more difficult than I thought.
The result so far is this:
My code looks like this:
- (void) drawCircleAnimated {
int radius = 100;
CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(20.0f, 64.0f, CGRectGetWidth(self.view.layer.bounds) - 40.0f, CGRectGetHeight(self.view.layer.bounds) - 84.0f);
[self.view.layer addSublayer:animationLayer];
CGRect pathRect = CGRectInset(animationLayer.bounds, 100.0f, 100.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = animationLayer.bounds;
pathLayer.bounds = pathRect;
pathLayer.geometryFlipped = NO;
pathLayer.path = path.CGPath;
pathLayer.strokeColor = [[UIColor blackColor] CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 10.0f;
pathLayer.lineJoin = kCALineJoinBevel;
[animationLayer addSublayer:pathLayer];
[self addStartPointCircle];
CAShapeLayer* circleToMove = [self addStartPointCircle];
circleToMove.anchorPoint = CGPointZero;
[pathLayer addSublayer:circleToMove];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 5.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
CAKeyframeAnimation *penAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
penAnimation.duration = 5.0;
penAnimation.path = pathLayer.path;
penAnimation.calculationMode = kCAAnimationPaced;
penAnimation.delegate = self;
penAnimation.removedOnCompletion = NO;
[circleToMove addAnimation:penAnimation forKey:#"position"];
}
To achieve the easing function, I've added this to the penAnimation - but it doesn't change anything. I guest it will always follow the other animation?
penAnimation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.75 * M_PI],
[NSNumber numberWithFloat:1.5 * M_PI], nil];
penAnimation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.5],
[NSNumber numberWithFloat:1.0], nil];
penAnimation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], // from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3
This should be all you need to add.
pathLayer.lineCap = kCALineCapRound;

Change position of UIImageView based on UIBezierPath

I am developing a circular progress bar using UIBezierPath. The progress bar changes its color and position based on a randomly generated float number.
So far the progress bar works fine. I used the following code in order to draw and animate the color:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:244.0/255.0 green:244.0/255.0 blue:244.0/255.0 alpha:1.0];
loader = [[UIImageView alloc]initWithFrame:CGRectMake(39, 110, 240, 130)];
loader.image = [UIImage imageNamed:#"loader.png"];
[self.view bringSubviewToFront:loader];
[self.view addSubview:loader];
// Draw the arc with bezier path
int radius = 100;
arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineCap = kCALineCapRound;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:10.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
// Gradient of progress bar
gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor ];
gradientLayer.startPoint = CGPointMake(0,0.1);
gradientLayer.endPoint = CGPointMake(0.5,0.2);
[self.view.layer addSublayer:gradientLayer];
gradientLayer.mask = arc;
refreshButton = [[UIButton alloc] initWithFrame:CGRectMake(50, 370, 50, 50)];
[refreshButton addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventTouchUpInside];
[refreshButton setBackgroundImage:[UIImage imageNamed:#"refresh.png"] forState:UIControlStateNormal];
[self.view addSubview:refreshButton];
smallButton = [[UIImageView alloc] initWithFrame:CGRectMake(285, 225, 15, 15)];
smallButton.image = [UIImage imageNamed:#"buttonSmall.png"];
[self.view addSubview:smallButton];
}
-(void)refresh:(id)sender{
NSLog(#"Refresh action:");
CGFloat randomValue = ( arc4random() % 256 / 256.0 );
NSLog(#"%f", randomValue);
valueLabel.text = #"";
valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 370, 100, 50)];
valueLabel.text = [NSString stringWithFormat: #"%.6f", randomValue];
[self.view addSubview:valueLabel];
// Animation of the progress bar
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
//drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
//drawAnimation.fillMode = kCAFillModeRemoved;
drawAnimation.fillMode = kCAFillModeForwards;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:randomValue];
drawAnimation.toValue = [NSNumber numberWithFloat:randomValue];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
if (randomValue >= 0.500000) {
gradientLayer.colors = #[(__bridge id)[UIColor purpleColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor ];
} else if (randomValue <= 0.089844){
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor redColor].CGColor ];
}
else{
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor ];
}
}
Now I want to change the position of the small button on the right side of the progress bar (see first image with the red arrow) based on the current position of the progress bar. I tried to change its frame and setting the x and y position of the image to randomValue but all i got was the image drawn at a random place on the screen.
Does anyone have an idea how to tackle this?
Any help would be greatly appreciated.
Thank you very much!
Granit
There are various ways to tackle this.
The simplest: Since your Bezier curve is a half-circle, use trig to calculate the position along your circle.
I noticed that you are animating your colored bar using strokeEnd values from 0 to 10. The valid values of strokeEnd range from 0.0 to 1.0. You should change that to animate from 0.0 to 1.0.
Once you've fixed that, you code is animating an arc from 0 to 180 degrees. (Actually, you're going backwards, from 180 degrees to 0 degrees, and trig in iOS is based on Radians, which range from 0 to 2π.
If you have an input number value, which ranges from 0 to 1, a CGPoint center that is the center of your arc, and a value r which is distance from the center of your button to the center of the circle (radius of the circle your button will travel along) then your point position would be:
angle = 2* M_PI * (1-value);
buttonCenter.x = center.x + r * cos(angle);
buttonCenter.y = center.y + r * sin(angle);
Another option that would let you animate it would be to create a transform that rotates the button around your center point. You could even use UIView animation to do this. (To build your transform: start with identity transform. Translate center point, rotate, translate back.)
The other options all involve using a bezier path or CGPath. You would need to create a path that mirrors the path of your colored arc but defines the placement of your button. (In your case just an arc with a larger radius.)
If you had a bezier path that was a simple cubic bezier curve, you could use the Bezier formula to calculate the x & y value of your point that way. If you were using a bezier path that consisted of multiple segments then you would need a way to get a point along an arbitrary CGPath. I don't know how to do this specifically, but it should be possible, since the system can animate layers along an arbitrary CGPath using keyframe animation.
The next option would be to use keyframe animation and animate your button along a GCPath. You should be able to animate it to an arbitrary point along it's path by manipulating the time settings of the animation. This is a little tricky and not very well documented.

Re-draw UIBezierPath based on random CGFloat value

I am developing a custom circular progress bar using UIBezierPath. I want to change the progress based on randomly generated CGFloat values.
My progress bar looks like this:
I drew the progress bar using the following code:
// Draw the arc with bezier path
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.cornerRadius = 100.0;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:10.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
// Gradient of progress bar
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor ];
gradientLayer.startPoint = CGPointMake(0,0.1);
gradientLayer.endPoint = CGPointMake(0.5,0.2);
[self.view.layer addSublayer:gradientLayer];
gradientLayer.mask = arc;
refreshButton = [[UIButton alloc] initWithFrame:CGRectMake(50, 370, 50, 50)];
[refreshButton addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventTouchUpInside];
[refreshButton setBackgroundImage:[UIImage imageNamed:#"refresh.png"] forState:UIControlStateNormal];
[self.view addSubview:refreshButton];
The UIButton with the green arrow generates randomly CGFloat values. So my question is how to reset the animation so that it stops at the corresponding position if it is between 0.0 and 1.0? I tried to insert the code for the animation inside the method of the button, but as a result the new arc was drawn on top of the existing one.
-(void)refresh:(id)sender{
NSLog(#"Refresh action:");
CGFloat randomValue = ( arc4random() % 256 / 256.0 );
NSLog(#"%f", randomValue);
valueLabel.text = #"";
valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 370, 100, 50)];
valueLabel.text = [NSString stringWithFormat: #"%.6f", randomValue];
[self.view addSubview:valueLabel];
// Draw the arc with bezier path
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.cornerRadius = 100.0;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:randomValue];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
I have no clue how to re-draw the arc, so any help or idea would be greatly appreciated!
Thanks a lot.
Granit
Try re-using the sublayer or replacing it. Make arc a class variable. Then cycle through the sublayers, remove the old arc then remake it with your new random float percentage. Once arc is a class variable then you can either replace it and animate from zero or you can animate from it's current percent to a new percent.

Resources