iOS Recipes - Matt Drance [51]
[self addSubview:shineView];
[shineView release];
for (CGFloat i = M_PI/10; i < M_PI*2; i += M_PI/7.5) {
PRPetal *petal = [[PRPetal alloc] initWithFrame:petalRect];
petal.outerColor = [UIColor yellowColor];
petal.innerColor = [UIColor colorWithRed:1 green:.8 blue:.2 alpha:1];
petal.lineThickness = 40;
petal.strokeColor = [UIColor whiteColor];
[shineView addSubview:petal];
[petal release];
petal.layer.anchorPoint = CGPointMake(.5, 2);
petal.transform = CGAffineTransformMakeRotation(i);
}
[self addRotationAnimation];
PRPSmile *sunCenter = [[PRPSmile alloc] initWithFrame:sunRect];
sunCenter.innerColor = [UIColor yellowColor];
sunCenter.outerColor = [UIColor colorWithRed:1 green:.8 blue:.2 alpha:1];
[self addSubview:sunCenter];
[sunCenter release];
Figure 30. Petals rotating and pulsing around the sun
* * *
The PRPSunshine class creates the sun from the same components used in the previous recipes, PRPetal and PRPSmile. We modify the colors slightly for a “sunnier” look and use a much thinner Rect for the petals. Here we also add the petals to a secondary UIView: shineView. shineView covers the same area as the main UIView but is used only to contain the petals. This allows us to add animation to the underlying layer without affecting the PRPSmile.
To create the circle of petals, we iterate through each of the required angles of rotation, in radians. A view will always rotate around its anchorPoint, which is usually set to the center of the Rect (0.5, 0.5). Because we need the petal to rotate around the center of the flower, rather than its own center, we need to set the anchorPoint to be below the lower edge of the Rect (0.5, 2). After that, a call to the CGAffineTransformMakeRotation method with the current radian value will ensure that the petal is positioned in the correct part of the flower and at the correct angle.
GraphicsGarden/PRPSunshine.m
CABasicAnimation *animation=[CABasicAnimation
animationWithKeyPath:@"transform.rotation"];
animation.duration=10;
animation.speed = self.animationSpeed;
animation.repeatCount = MAXFLOAT;
animation.fromValue=[NSNumber numberWithFloat:0];
animation.toValue= [NSNumber numberWithFloat:M_PI*2];
[self.shineLayer addAnimation:animation forKey:@"rotate"];
animation.keyPath = @"opacity";
animation.duration=.5;
animation.autoreverses = YES;
animation.fromValue=[NSNumber numberWithFloat:0.7];
animation.toValue= [NSNumber numberWithFloat:1.0];
[self.shineLayer addAnimation:animation forKey:@"fade"];
animation.keyPath = @"transform.scale";
animation.fromValue=[NSNumber numberWithFloat:.9];
animation.toValue= [NSNumber numberWithFloat:1.1];
[self.shineLayer addAnimation:animation forKey:@"scale"];
The three animations we are building in the addRotationAnimation method share many attributes, which means we can reuse the same animation object by simply modifying the properties that differ. Two of the animations are actually transformations that make use of CATransform3D, but because we are using Key-Path extensions, we do not need to construct the transformations ourselves. We can simply set the “from” and “to” values as NSNumbers, and the animation will construct the transformations for us. Using the Key-Path extensions comes with the added benefit of allowing the rotation to repeatedly turn a full circle. If we had used CATransform3D values for the rotation with the same angles, it would not have animated, because the starting angle and ending angle, 0 and 2*pi, respectively, would be effectively the same.
Adding the animation to a CALayer creates its own copy and further changes to that animation object have no effect on the CALayer. You can change any of the animation properties simply by adding an updated version. As long as you use the same key name for the new animation, you won’t need to remove the old copy of the animation from the CALayer.
Recipe 25 Make Composited and Transformed Views
Problem
You want to build an image from components that you have already built, transforming some of the individual