Change position of UIImageView based on UIBezierPath - ios

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.

Related

CABasicAnimation used for a chronometer animation doesn't work with iOS 8+

Some year ago i have used this code for realize a chronometer animation.
Was a cute animation but doesn't work with iOS8>
A circle should appears and lose a pie every 0.1sec but The animation doesn't start. It works on my iPhone 5 iOS 7.1
I tried to solve it but after 2 hours i have no solution.
Can someone with more experience with CABasicAnimation help me?
THANK YOU.
This is the code:
//Animazione CRONOMETRO
-(void) startCronometro{
//SetTime
counterStart = TIME;
[sfondoCronometro setImage:[UIImage imageNamed:#"cronoStart.png"]];
maskLayer = [CAShapeLayer layer];
CGFloat maskHeight = sfondoCronometro.frame.size.height;
CGFloat maskWidth = sfondoCronometro.frame.size.width;
CGPoint centerPoint;
centerPoint = CGPointMake(sfondoCronometro.frame.size.width/2, (sfondoCronometro.frame.size.height/2));
//Make the radius of our arc large enough to reach into the corners of the image view.
CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;
//Don't fill the path, but stroke it in black.
maskLayer.fillColor = [[UIColor clearColor] CGColor];
maskLayer.strokeColor = [[UIColor blackColor] CGColor];
maskLayer.lineWidth = 25;
CGMutablePathRef arcPath = CGPathCreateMutable();
//Move to the starting point of the arc so there is no initial line connecting to the arc
CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);
//Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
CGPathAddArc(arcPath, nil, centerPoint.x, centerPoint.y, radius/2, 3*M_PI/2, -M_PI/2, NO);
maskLayer.path = arcPath;//[aPath CGPath];//arcPath;
//Start with an empty mask path (draw 0% of the arc)
maskLayer.strokeEnd = 0;
CFRelease(arcPath);
//Install the mask layer into out image view's layer.
sfondoCronometro.layer.mask = maskLayer;
//Set our mask layer's frame to the parent layer's bounds.
sfondoCronometro.layer.mask.frame = sfondoCronometro.layer.bounds;
//Create an animation that increases the stroke length to 1, then reverses it back to zero.
CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
swipe.duration = TIME;
NSLog(#"TIME: %f", swipe.duration);
swipe.delegate = self;
// [swipe setValue:#"string" forKey:#"key"];
swipe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
swipe.fillMode = kCAFillModeForwards;
swipe.removedOnCompletion = NO;
swipe.toValue = [NSNumber numberWithFloat: 1.0];
[maskLayer addAnimation: swipe forKey: #"strokeEnd"];
}
I seem to remember that at some point the function of CGPathAddArc changed, and depending on the values of your start and end angle, you had to flip the clockwise flag on the call. Try using clockwise = YES.
I just did a little testing in an app of mine and confirmed this. For the angles you're using, clockwise NO works for iOS <= 7, but fails for iOS >=8.
Switch the last parameter from NO to YES.

CAShapeLayer rendering issue

I'm trying to create circle animation by adding CAShapeLayers with different stroke colors:
UIView *view = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:view];
view.backgroundColor = [UIColor redColor];
[self addCircleAnimationTo:view direction:YES];
- (void)addCircleAnimationTo:(UIView *)view direction:(BOOL)direction {
CGFloat animationTime = 3;
// Set up the shape of the circle
int radius = view.frame.size.width/2;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in view
circle.position = CGPointMake(radius,
radius);
circle.bounds = view.bounds;
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
// switch color
circle.strokeColor = (direction) ? [UIColor blueColor].CGColor : [UIColor whiteColor].CGColor;
circle.lineWidth = 5;
circle.lineJoin = kCALineJoinBevel;
circle.strokeStart = .0;
circle.strokeEnd = 1.;
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = animationTime;
drawAnimation.repeatCount = 1;
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
// Add to parent layer
[view.layer addSublayer:circle];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((animationTime-0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self addCircleAnimationTo:view direction:!direction];
});
}
I'm wondering why I can see blue when white layer animates and how to get rid of this weird blue color border:
You know that you are adding more and more shape layers after each call to addCircleAnimationTo ?
While you animate the new layer the old shape layer reminds below the new one. That is why you see it.
[EDIT 2] SHORT EXPLANATION
We create two layers first with blue path colour, second with white path colour.
To first layer we add fill animation - change of layer.strokeEnd property, we animate it from 0 to 1 and from 1 to 1. Half of duration from 0 to 1 and half of duration from 1 to 1 (visually nothing happens, but is needed).
We add clear animation - change of layer.strokeStart, we animate it from 0 to 1. Duration of this animation is half of duration of fill animation. Begin time is also half of fill animation because we want to move beginning of stroke when end stroke is still.
We add the same animations to second, white layer but with appropriate begin time offset, thanks to that white path unwinds and blue path winds.
[EDIT] ADDED REAL ANSWER
Here is my suggestion to solve your animation problem. Hope it helps ... :)
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:view];
view.backgroundColor = [UIColor redColor];
self.circle1 = [self circle:view.bounds];
self.circle1.strokeColor = [UIColor blueColor].CGColor;
self.circle2 = [self circle:view.bounds];
self.circle2.strokeColor = [UIColor whiteColor].CGColor;
[view.layer addSublayer:self.circle1];
[view.layer addSublayer:self.circle2];
[self.circle1 addAnimation:[self fillClearAnimation:0] forKey:#"fillclear"];
[self.circle2 addAnimation:[self fillClearAnimation:3] forKey:#"fillclear"];
}
- (CAAnimationGroup *)fillClearAnimation:(CFTimeInterval)offset
{
CGFloat animationTime = 3;
CAAnimationGroup *group = [CAAnimationGroup animation];
CAKeyframeAnimation *fill = [CAKeyframeAnimation animationWithKeyPath:#"strokeEnd"];
fill.values = #[ #0,#1,#1];
fill.duration = 2*animationTime;
fill.beginTime = 0.f;
CAKeyframeAnimation *clear = [CAKeyframeAnimation animationWithKeyPath:#"strokeStart"];
clear.values = #[ #0, #1];
clear.beginTime = animationTime;
clear.duration = animationTime;
group.animations = #[ fill, clear ];
group.duration = 2*animationTime;
group.beginTime = CACurrentMediaTime() + offset;
group.repeatCount = FLT_MAX;
return group;
}
- (CAShapeLayer *)circle:(CGRect)frame
{
CAShapeLayer *circle = [CAShapeLayer layer];
CGFloat radius = ceil(frame.size.width/2);
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in view
circle.position = CGPointMake(radius,
radius);
circle.frame = frame;
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.lineWidth = 5;
circle.lineJoin = kCALineJoinBevel;
circle.strokeStart = 0.f;
circle.strokeEnd = 0.f;
return circle;
}
The antialiasing is what's causing the problem
When the white CAShapeLayer gets drawn over the blue, the edge gets blended with the previous edge blending that the antialiasing created.
The only real solution is to clear the blue from underneath the white as you animate. You can do this by animating the strokeStart property of the previous layer from 0.0 to 1.0. You should be able to re-use your drawAnimation for this, by just changing the keyPath. For example, change your dispatch_after to:
- (void)addCircleAnimationTo:(UIView *)view direction:(BOOL)direction {
// Insert your previous animation code, where drawAnimation is defined and added to the circle layer.
drawAnimation.keyPath = #"strokeStart"; // Re-use the draw animation object, setting it to animate strokeStart now.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((animationTime-0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self addCircleAnimationTo:view direction:!direction];
[circle addAnimation:drawAnimation forKey:#"(un)drawCircleAnimation"]; // Sets the previous layer to animate out
});
}
That way the antialiasing edge blending gets cleared from the blue layer, allowing the white layer to get a nice & clean antialiasing. Just don't forget to remove the layer once you've animated it out!
Another potential solution is to increase the stroke width of the white layer, so it strokes over the blue layer's edge blending, however I would imagine it would create an undesired effect. You could also disable antialiasing, but that would look horrible on the circle.

Change height of layer using caanimation

I am learning the caanimations and I face an issue.
I want to display a rectangle which height start to 1 and goes to 100. I want this rectangle to stay in the middle of the view and just expand its height.
this is the code i made so far:
CAShapeLayer *rect = [CAShapeLayer layer];
CGRect frame = CGRectMake(0, 0,destView.frame.size.width, 1);
rect.frame = frame;
rect.path = [UIBezierPath bezierPathWithRect:frame].CGPath;
//DestView is the view that will receive this layer
rect.position = destView.center;
//For debug purpose
rect.fillColor = [UIColor blackColor].CGColor;
rect.strokeColor = [UIColor redColor].CGColor;
rect.anchorPoint = CGPointMake(0.5, 0.5);
CABasicAnimation *test = [CABasicAnimation animationWithKeyPath:#"bounds.size.height"];
[test setFromValue:[NSNumber numberWithFloat:1.0f]];
[test setToValue:[NSNumber numberWithFloat:100.0f]];
test.duration = 5.0f;
[test setFillMode:kCAFillModeForwards];
[rect addAnimation:test forKey:#"rectGrowing"];
[destView.layer addSublayer:rect];
The result I have is the redline (rectangle with 1 height and red stroke) moving from bottom to middle... The height does not increase.
From your code, the height should increase.
However you should use backgroundColor instead of fillColor.
This is because although the height changed, the path does not however.

CABasicAnimation not working on device (iPhone 5s)

I have a CABasicAnimation that creates an iris wipe effect on an image. In short, the animation works fine on the simulator but there is no joy on device. The timers still fire correctly and the animationCompleted block gets called however there is no visible animation.
Here is the code to get the iris wipe working:
- (void)irisWipe
{
animationCompletionBlock theBlock;
_resultsImage.hidden = FALSE;//Show the image view
[_resultsImage setImage:[UIImage imageNamed:#"logoNoBoarder"]];
[_resultsImage setBackgroundColor:[UIColor clearColor]];
[_resultsImage setFrame:_imageView.bounds];
//Create a shape layer that we will use as a mask for the waretoLogoLarge image view
CAShapeLayer *maskLayer = [CAShapeLayer layer];
CGFloat maskHeight = _resultsImage.layer.bounds.size.height;
CGFloat maskWidth = _resultsImage.layer.bounds.size.width;
CGPoint centerPoint;
centerPoint = CGPointMake( maskWidth/2, maskHeight/2);
//Make the radius of our arc large enough to reach into the corners of the image view.
CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;
// CGFloat radius = MIN(maskWidth, maskHeight)/2;
//Don't fill the path, but stroke it in black.
maskLayer.fillColor = [[UIColor clearColor] CGColor];
maskLayer.strokeColor = [[UIColor blackColor] CGColor];
maskLayer.lineWidth = radius; //Make the line thick enough to completely fill the circle we're drawing
// maskLayer.lineWidth = 10; //Make the line thick enough to completely fill the circle we're drawing
CGMutablePathRef arcPath = CGPathCreateMutable();
//Move to the starting point of the arc so there is no initial line connecting to the arc
CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);
//Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
CGPathAddArc(arcPath,
nil,
centerPoint.x,
centerPoint.y,
radius/2,
3*M_PI/2,
-M_PI/2,
NO);
maskLayer.path = arcPath;
//Start with an empty mask path (draw 0% of the arc)
maskLayer.strokeEnd = 1.0;
CFRelease(arcPath);
//Install the mask layer into out image view's layer.
_resultsImage.layer.mask = maskLayer;
//Set our mask layer's frame to the parent layer's bounds.
_resultsImage.layer.mask.frame = _resultsImage.layer.bounds;
//Create an animation that increases the stroke length to 1, then reverses it back to zero.
CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
swipe.duration = 1;
swipe.delegate = self;
[swipe setValue: theBlock forKey: kAnimationCompletionBlock];
swipe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
swipe.fillMode = kCAFillModeForwards;
swipe.removedOnCompletion = NO;
swipe.autoreverses = NO;
swipe.toValue = [NSNumber numberWithFloat: 0];
//Set up a completion block that will be called once the animation is completed.
theBlock = ^void(void)
{
NSLog(#"completed");
};
[swipe setValue: theBlock forKey: kAnimationCompletionBlock];
// doingMaskAnimation = TRUE;
[maskLayer addAnimation: swipe forKey: #"strokeEnd"];
}
Is there something in iOS7 I should be aware of when working with CAAnimations etc? OR is there a error in the code?
Note this code was sourced from: How do you achieve a "clock wipe"/ radial wipe effect in iOS?
I think the problem (or at least part of it) may be this line:
[_resultsImage setFrame:_imageView.bounds];
That should read
[_resultsImage setBounds:_imageView.bounds];
Instead. If you set the FRAME to the bounds of the image view, you're going to move the image view to 0.0 in its superview.
I'm also not clear what _imageView is, as opposed to _resultsImage.
I would step through your code in the debugger, looking at the frame rectangles that are being set for the image view and mask layer, and all the other values that are calculated.

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