CAShapeLayer animation doesn't stay on screen but disappears
Asked Answered
C

4

3

I'm trying to draw a animated circle but every segment needs to have another color. Now everything works except that my piece that is just drawed before I call the method again disappears so only the last part stays. I don't want that, I want that after 4 times a whole circle is drawn in different color strokes.

How can I fix this that it doesn't disappears?

This is my init code:

- (void)_initCircle {
    _circle = [CAShapeLayer layer];
    _radius = 100;

    // 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);

    _circle.fillColor = [UIColor clearColor].CGColor;
    _circle.lineWidth = 5;

    // Add to parent layer
    [self.view.layer addSublayer:_circle];
}

This is my draw piece:

- (void)_drawStrokeOnCircleFrom:(float)start to:(float)end withColor:(UIColor *)color {
    // Configure the apperence of the circle
    _circle.strokeColor = color.CGColor;
    _circle.strokeStart = start;
    _circle.strokeEnd = end;

       [CATransaction begin];

    // Configure animation
    CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    drawAnimation.duration            = 2.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:start];
    drawAnimation.toValue   = [NSNumber numberWithFloat:end];

    // Experiment with timing to get the appearence to look the way you want
    drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

    [CATransaction setCompletionBlock:^{
        NSLog(@"Complete animation");
        _index++;
        if(_index < _votes.count){
        _startStroke = _endStroke;
        _endStroke += [_votes[_index] floatValue] / _totalListens;
        [self _drawStrokeOnCircleFrom:_startStroke to:_endStroke withColor:_colors[_index]];
        }

    }];

    // Add the animation to the circle
    [_circle addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

       [CATransaction commit];
}
Cerebral answered 23/3, 2015 at 12:6 Comment(0)
C
0

I solved the problem with extra layers like this:

- (void)_drawStrokeOnCircle {

    [self _initCircle];

    [CATransaction begin];

    for (NSUInteger i = 0; i < 4; i++)
    {
        CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
        strokePart.fillColor = [[UIColor clearColor] CGColor];
        strokePart.frame = _circle.bounds;
        strokePart.path = _circle.path;
        strokePart.lineCap = _circle.lineCap;
        strokePart.lineWidth = _circle.lineWidth;


        // Configure the apperence of the circle
        strokePart.strokeColor = ((UIColor *)_colors[i]).CGColor;
        strokePart.strokeStart = [_segmentsStart[i] floatValue];
        strokePart.strokeEnd = [_segmentsEnd[i] floatValue];

        [_circle addSublayer: strokePart];

        // Configure animation
        CAKeyframeAnimation* drawAnimation = [CAKeyframeAnimation animationWithKeyPath: @"strokeEnd"];
        drawAnimation.duration            = 4.0;

        // Animate from no part of the stroke being drawn to the entire stroke being drawn
        NSArray* times = @[ @(0.0),
                            @(strokePart.strokeStart),
                            @(strokePart.strokeEnd),
                            @(1.0) ];
        NSArray* values = @[ @(strokePart.strokeStart),
                             @(strokePart.strokeStart),
                             @(strokePart.strokeEnd),
                             @(strokePart.strokeEnd) ];

        drawAnimation.keyTimes = times;
        drawAnimation.values = values;
        drawAnimation.removedOnCompletion = NO;
        drawAnimation.fillMode = kCAFillModeForwards;

        // Experiment with timing to get the appearence to look the way you want
        drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];


        [strokePart addAnimation: drawAnimation forKey: @"drawCircleAnimation"];
    }
    [CATransaction commit];
}

- (void)_initCircle {
    [_circle removeFromSuperlayer];
    _circle = [CAShapeLayer layer];
    _radius = 100;

    // 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);

    _circle.fillColor = [UIColor clearColor].CGColor;
    _circle.lineWidth = 5;

    // Add to parent layer
    [self.view.layer addSublayer:_circle];
}
Cerebral answered 23/3, 2015 at 13:33 Comment(0)
M
7

Try setting these values on your CABasicAnimation:

drawAnimation.removedOnCompletion = NO;
drawAnimation.fillMode = kCAFillModeForwards;
Morissa answered 23/3, 2015 at 12:58 Comment(2)
Sorry, I got your problem not right. Those two lines keep your animation persistent after it completes, which is something you need. But you also want to have a multicolored stroke, if I got that correctly? Then you need to have mutliple layers, each with a stroke segment with a different color.Morissa
Jup that was the problem. I needed more layers. I've posted my solution :) Thanks for the help btw. Added fillmode too, the removedOnCompletion isn't necessary anymore.Cerebral
I
2

That worked for me using "kCAFillModeBoth", both statements are necessary:

//to keep it after finished
animation.removedOnCompletion = false;
animation.fillMode = kCAFillModeBoth;
Inglebert answered 4/7, 2015 at 2:58 Comment(0)
C
0

I solved the problem with extra layers like this:

- (void)_drawStrokeOnCircle {

    [self _initCircle];

    [CATransaction begin];

    for (NSUInteger i = 0; i < 4; i++)
    {
        CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
        strokePart.fillColor = [[UIColor clearColor] CGColor];
        strokePart.frame = _circle.bounds;
        strokePart.path = _circle.path;
        strokePart.lineCap = _circle.lineCap;
        strokePart.lineWidth = _circle.lineWidth;


        // Configure the apperence of the circle
        strokePart.strokeColor = ((UIColor *)_colors[i]).CGColor;
        strokePart.strokeStart = [_segmentsStart[i] floatValue];
        strokePart.strokeEnd = [_segmentsEnd[i] floatValue];

        [_circle addSublayer: strokePart];

        // Configure animation
        CAKeyframeAnimation* drawAnimation = [CAKeyframeAnimation animationWithKeyPath: @"strokeEnd"];
        drawAnimation.duration            = 4.0;

        // Animate from no part of the stroke being drawn to the entire stroke being drawn
        NSArray* times = @[ @(0.0),
                            @(strokePart.strokeStart),
                            @(strokePart.strokeEnd),
                            @(1.0) ];
        NSArray* values = @[ @(strokePart.strokeStart),
                             @(strokePart.strokeStart),
                             @(strokePart.strokeEnd),
                             @(strokePart.strokeEnd) ];

        drawAnimation.keyTimes = times;
        drawAnimation.values = values;
        drawAnimation.removedOnCompletion = NO;
        drawAnimation.fillMode = kCAFillModeForwards;

        // Experiment with timing to get the appearence to look the way you want
        drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];


        [strokePart addAnimation: drawAnimation forKey: @"drawCircleAnimation"];
    }
    [CATransaction commit];
}

- (void)_initCircle {
    [_circle removeFromSuperlayer];
    _circle = [CAShapeLayer layer];
    _radius = 100;

    // 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);

    _circle.fillColor = [UIColor clearColor].CGColor;
    _circle.lineWidth = 5;

    // Add to parent layer
    [self.view.layer addSublayer:_circle];
}
Cerebral answered 23/3, 2015 at 13:33 Comment(0)
P
0

SWIFT 4, Xcode 11:

let strokeIt = CABasicAnimation(keyPath: "strokeEnd")
let timeLeftShapeLayer = CAShapeLayer()

and in your viewDidLoad

  override func viewDidLoad() {
    super.viewDidLoad()
    drawTimeLeftShape()
    strokeIt.fillMode = CAMediaTimingFillMode.forwards
    strokeIt.isRemovedOnCompletion = false
    strokeIt.fromValue = 0
    strokeIt.toValue = 1
    strokeIt.duration = 2
    timeLeftShapeLayer.add(strokeIt, forKey: nil)
  }

and by UIBezierPathcreate function to draw circle:

func drawTimeLeftShape() {
        timeLeftShapeLayer.path = UIBezierPath(
            arcCenter: CGPoint(x: myView.frame.midX , y: myView.frame.midY),
            radius: myView.frame.width / 2,
            startAngle: -90.degreesToRadians,
            endAngle: 270.degreesToRadians,
            clockwise: true
        ).cgPath
        timeLeftShapeLayer.strokeColor = UIColor.red.cgColor
        timeLeftShapeLayer.fillColor = UIColor.clear.cgColor
        timeLeftShapeLayer.lineWidth = 1
        timeLeftShapeLayer.strokeEnd = 0.0
        view.layer.addSublayer(timeLeftShapeLayer)
    }
Pyrogallate answered 27/1, 2020 at 13:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.