I'm not 100% clear on what you want here, but if the goal is just for the whole stroke to change color as it draws, but in three discrete stages, then I would propose adding the following. I cooked up these examples in a default "Single View Application" template. I've got a button set up with its action pointing at -doStuff:
. If the whole stroke color were to change, it might look something like this:
To produce that, the code looked like:
@implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.strokeEnd = 0.0;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];// Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(@"Done"); }];
const double portion = 1.0 / ((double)numSegments);
NSMutableArray* values = [NSMutableArray arrayWithCapacity: numSegments];
NSMutableArray* times = [NSMutableArray arrayWithCapacity: numSegments + 1];
for (NSUInteger i = 0; i < numSegments; i++)
{
[values addObject: (__bridge id)[[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor]];
[times addObject: @(i * portion)];
}
[times addObject: @(1.0)]; // Have to add this, otherwise the last value wont get used.
{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: @"strokeColor"];
animation.keyTimes = times;
animation.values = values;
animation.calculationMode = kCAAnimationDiscrete;
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: @"strokeColor"];
}
{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath: @"strokeEnd"];
animation.fromValue = @(0);
animation.toValue = @(1);
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: @"strokeEnd"];
}
}
[CATransaction commit];
}
@end
Alternately, if the goal is to have three different segments of the stroke, all with different colors, that's a little more complicated, but can still be done with the same basic principals. One thing to note is that, without custom drawing, your CAShapeLayer
s can't have more than one stroke color (AFAIK), so you'll need to break this up into several sublayers.
This next example puts a shape layer into the view and then adds the sublayers for each part of the stroke and sets up the animation such that it appears theres a single, multi-color stroke being drawn, where each segment is a separate color. Here's roughly what it looked like:
Here's the code:
@implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];//Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(@"Done"); }];
const double portion = 1.0 / ((double)numSegments);
for (NSUInteger i = 0; i < numSegments; i++)
{
CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
strokePart.fillColor = [[UIColor clearColor] CGColor];
strokePart.frame = mLayer.bounds;
strokePart.path = mLayer.path;
strokePart.lineCap = mLayer.lineCap;
strokePart.lineWidth = mLayer.lineWidth;
// These could come from an array or whatever, this is just easy...
strokePart.strokeColor = [[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor];
strokePart.strokeStart = i * portion;
strokePart.strokeEnd = (i + 1) * portion;
[mLayer addSublayer: strokePart];
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: @"strokeEnd"];
NSArray* times = @[ @(0.0), // Note: This works because both the times and the stroke start/end are on scales of 0..1
@(strokePart.strokeStart),
@(strokePart.strokeEnd),
@(1.0) ];
NSArray* values = @[ @(strokePart.strokeStart),
@(strokePart.strokeStart),
@(strokePart.strokeEnd),
@(strokePart.strokeEnd) ];
animation.keyTimes = times;
animation.values = values;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[strokePart addAnimation: animation forKey: @"whatever"];
}
}
[CATransaction commit];
}
@end
I'm not sure I've exactly understood what you were going for, but hopefully one of these is helpful.