iOS Recipes - Matt Drance [54]
CGColorRef color = [UIColor blackColor].CGColor;
UIColor *color1 = [UIColor colorWithRed:0.01 green:0.20 blue:0.80
alpha:1.0];
UIColor *color2 = [UIColor colorWithRed:1.00 green:0.50 blue:0.00
alpha:1.0];
UIColor *color3 = [UIColor colorWithRed:0.35 green:0.74 blue:0.11
alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)[color1 CGColor],
[color2 CGColor],
[color3 CGColor],
nil];
CAGradientLayer *gLayer = (CAGradientLayer *)self.layer;
gLayer.colors = colors;
gLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:0.9],
nil];
gLayer.startPoint = CGPointMake(0.5, 0);
gLayer.endPoint = CGPointMake(0.5, 1);
The animation code should be starting to look familiar; it follows the standard pattern of a CABasicAnimation setup. We could have used a CAAnimationGroup to ensure that the two animations run concurrently, but the result would be the same because they share the same duration and timing function.
GraphicsGarden/GradientView.m
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:
@"startPoint"];
anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0.5, 1)];
anim.duration = Duration
anim.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut];
[gLayer addAnimation:anim forKey:@"start"];
anim = [CABasicAnimation animationWithKeyPath:@"colors"];
anim.fromValue = [NSArray arrayWithObjects:(id)color, color, color, nil];
anim.duration = Duration;
anim.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut];
[gLayer addAnimation:anim forKey:@"colors"];
The first animation object applies to the Startpoint property, which sets the start position of the gradient, specified in unit coordinates, transitioning from 1.0 (bottom of view) to 0 (top of view). This produces the effect of a gradual rise up the screen. Adding a kCAMediaTimingFunctionEaseOut timing function causes the animation to start quickly and then transition to a slower stop.
The second animation block drives the change of color. When the animation object is built, only the fromValue needs to be set to the array of colors from which we want the transition to begin—in this case, all black. The animation object interpolates to the colors currently stored in the layer. This technique would not work if we were adding the animation object after the view had already been rendered, because the gradient would appear in full, then disappear, and then animate back into view.
GraphicsGarden/MainViewController.m
GradientView *gradView = [[GradientView alloc] initWithFrame:
self.view.bounds];
[self.view addSubview:gradView];
[gradView release];
Adding the GradientView to the main view, as shown here, does two things. First, the GradientView becomes a subview of the main background view, and second, it triggers the initialization and animation of the underlying gradient layer. We get this result because we placed the code for those functions in the didMoveToSuperview delegate method. This method is called only when the addSubview method is called—the advantage being that the instantiation of the gradientView is separated from its activation, so we avoid any issues with timing the animation. If we had added gradient code to the initWithFrame method, it’s possible that the animation would have started before the view was rendered.
You can use this technique to create quite complex gradients, because you can specify as many colors and control points as you want. By modifying the animation timing, along with the start or end color arrays, you can produce a variety of eye-catching effects.
Could you have done this another way? Filling a view with a gradient is relatively simple, as you saw in Recipe 23, Draw Gradient-Filled Bezier Paths . But without this technique, animating that gradient, especially getting the cross-fade effect, would require a lot more code and be a great deal more processor-intensive.
Recipe 27 Reshape Shadows
Problem
You can add the impression