I'm using the following code to draw part of a circle. I'm a bit confused, however, because at the end of the animation it seems to immediately fill in the rest of the circle. Why is this happening?
// Set up the shape of the circle
int radius = 62;
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 self.view
circle.position = CGPointMake(18, 92);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor whiteColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[cell.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = .8; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// 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:0.4f];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
This is happening because by default a CAAnimation is removed from the layer when it completes. This means that it will remove the value that you animated to and set it to the default of 1.0 right after the animation finished. Since you are using a CAAnimation the layer is never actually changing its properties it just only looks like it is which is why when the animation is removed it gets set to 1.0 because that is what the layer's value for strokeEnd is since you never changed it. Try to see this yourself by printing the value of strokeEnd while the animation is happening.
This can be solved in 2 ways, setting the final storkeEnd value before you start the animation so that when it is removed it is still 0.4 or by adding certain properties to your CABasicAnimation. The properties you need to set to achieve what you are looking for are:
drawAnimation.fillMode = kCAFillModeForwards
and
drawAnimation.removedOnCompletion = false
Hope that helps
For the whole stroke to be animated drawAnimation.fromValue should be 0.0 and drawAnimation.toValue should be 1.0. These values determine what percentage of the stroke is animated.
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
fromValue : Defines the value the receiver uses to start interpolation.
toValue : Defines the value the receiver uses to end interpolation.
What you used was 0.4 for drawAnimation.toValue. So it ends at 40% of the animation and draws the rest in one go without animating.
For example if you set drawAnimation.fromValue = 0.5 and drawAnimation.toValue = 1.0,
animation will start from half a circle.
if you set drawAnimation.fromValue = 0.0 and drawAnimation.toValue = 0.5,
animation will end at half a circle and draw the rest without animation.
If you only wanted to draw 40% of the circle, you can use a different function to create the circle path.
CGFloat startAngle = (-M_PI/2);
CGFloat endAngle = startAngle + 0.4*(2*M_PI);
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES].CGPath;
The above code actually generates 40% of a circle path from startAngle = -M_PI/2. You can vary the angle according to your needs.
Related
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.
I am working on one of the school application. In which, based on the percentage achieved by student respective piechart should be drawn with an animation.
For example, if student got only 50%, the pie chart needs to be filled with 50% with an animation.
Can you please help me on this. I used the below code,
// Set up the shape of the circle
int radius = 60;
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 self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 10;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 10.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// 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:0.75f];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
I would like to stop the animation at 50%.
Please use the circle.strokeEnd = 0.5f;
It will automatically stops at 50%,
if you give 1.0f it goes to 100%
I am aiming to reveal a menu with a circular reveal scaling animation. For this I first set the mask to a radius with a radius of 0 at the beginning.
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
CAShapeLayer *mask = [CAShapeLayer new];
mask.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25, 25) radius:0 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES].CGPath;
self.layer.mask = mask;
Then I animate the new mask with a CABasicAnimation.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.toValue = (id)[UIBezierPath bezierPathWithArcCenter:CGPointMake(25, 25) radius:self.frame.size.height*1.2 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES].CGPath;
animation.removedOnCompletion = NO;
animation.duration = 0.45f;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.layer.mask addAnimation:animation forKey:nil];
On the first look this is working but the problem is that the animation itself is more oval than circular: http://i.stack.imgur.com/fqPYJ.png
The code is inspired by Animate circular mask of UIImageView in iOS.
Your code looks reasonable. (Although if you're always using a full circle you could probably use the simpler bezierPathWithOvalInRect method instead of bezierPathWithArcCenter).
It's hard to tell what I'm looking at in the image you linked. The two layer are white so we can't tell what is what. You might want to set one of the layers to a colored background while you're testing.
As to why you would get an oval: Do you have a transform on any of your layers? Specifically a scale transform? That would cause your distortion.
I'd like to draw a circle without filling (only border of the circle) step by step (like animated timer). 1 spin is equal 1 day (24 hours). I really don't know what to do.
Steps I've made
1) I've tried https://github.com/danielamitay/DACircularProgress (it's too wide line of progress)
2) I've tried to draw a circle with many arcs.
Can you put me some code please. I really confused. Thanks in advance.
EDIT
I'd like to use NSTimer because I have a button which allow user to stop drawning a circle. If user touch a button again - drawning will have to continue.
What I would do is to create a path that is a circle and use that with a CAShapeLayer and animate the strokeEnd similar to what I did in this answer.
It would look something like this (but I didn't run this code so there may be typos and other mistakes):
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:0
endAngle:2.0*M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.bounds = CGRectMake(0, 0, 2.0*radius, 2.0*radius);
circleLayer.path = circle.CGPath;
circleLayer.strokeColor = [UIColor orangeColor].CGColor;
circleLayer.lineWidth = 3.0; // your line width
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 10.0; // your duration
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = #0;
drawAnimation.toValue = #1;
Just note that both the path and the shape layer has a position so the circle path should be defined relative to the origin of the shape layers frame. It might be clearer to define the shape layer first and then create an oval inside of its bounds:
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.bounds = CGRectMake(0, 0, 2.0*radius, 2.0*radius);
circleLayer.position = center; // Set center of the circle
// Create a circle inside of the shape layers bounds
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:circleLayer.bounds];
circleLayer.path = circle.CGPath;
// Same appearance configuration as before
circleLayer.strokeColor = [UIColor orangeColor].CGColor;
circleLayer.lineWidth = 3.0; // your line width
If DACircleProgressenter link description here otherwise works for you, it looks like you can easily set the line thickness.
As opposed to have a simple lineWidth type property, it seems the author of that library sets the thickness based on a ratio to the radius of the circle. This exists as the thicknessRatio property of that class. For example, if your radius is 40, then setting thicknessRatio to 0.025 should yield a line width of 1. That library seems simple and well thought out - consider using it, or learning from it.
The default is set to 0.3, so a circle with a radius of 40 would have a progress line thickness of 12. That's probably what you were seeing.
Good luck!
I'd like to display an animating circle in my UITableViewCell. I've gotten the following code to work for self.view, but when I try to add it to cell.accessoryview it never draws. Anybody have any ideas on why it's not showing up?
- (void)makeCircleAtIndexPath:(NSIndexPath *)path {
UITableViewCell *cell = [_permanentSoundTableView cellForRowAtIndexPath:path];
// Set up the shape of the circle
int radius = 10;
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 self.view
circle.position = CGPointMake(cell.frame.size.width-10,cell.frame.size.height-5);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
UIColor *transparentBlack = [UIColor colorWithRed:250.0f green:250.0f blue:250.0f alpha:.5];
circle.strokeColor = transparentBlack.CGColor;
circle.lineWidth = 5;
// Add to parent layer
[cell.accessoryView.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 8.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = NO; // Remain stroked after the animation..
// 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.0f];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
Try adding your animation layer inside the cellForRowAtIndexPath: method, and make sure accessoryView of the cell is not nil. If it's nil, then add empty UIView with your animation layer's size, and then adding the layer. Good Luck!