iOS Recipes - Matt Drance [60]
The contentsRect property is the key to splitting up the image. We set the contents of each layer to contain the complete image, but by varying the contentsRect, we can constrain each subsequent strip to display only the portion of the image we want. By laying out each of the strips in a row, they appear identical to the original view. We use the parent layer here, stripLayer, only to contain our row of strips; it has no visible contents of its own.
ViewTransition/PRPViewTransition.m
- (void)cutLayersFromImage:(UIImage *)image {
width = self.bounds.size.width/numLayers;
height = self.bounds.size.height;
unitSize = 1.0/numLayers;
stripLayer = [CALayer layer];
[self.layer addSublayer:stripLayer];
for (int i = 0; i < numLayers; i++) {
CALayer *layer = [CALayer layer];
layer.contents = (id)image.CGImage;
CGRect posRect = CGRectMake(width*i, 0, width, height);
layer.contentsRect = CGRectMake(unitSize*i, 0, unitSize, 1);
layer.frame = posRect;
[stripLayer addSublayer:layer];
}
}
The process involved in the createRotatingLayers method can be a little hard to picture but ultimately creates a multisided “tube” of transparent layers that are equal in size to the image strips we created earlier. The trick to building the tube is to add each of the “side” layers while gradually incrementing both the rotation and the translation transformation with a zPosition—the point around which the layer rotates, set to be equal to the radius of the tube cross section. To try to visualize this process in action, imagine building a tube out of identical strips of paper and adding each strip as a new side, rotated a little from the previous one, until we complete the circle.
Each of these tube layers is added to the transform layer, which then lets us use sublayer transformation to rotate the tube of layers as a single unit.
ViewTransition/PRPViewTransition.m
- (void) createRotatingLayers {
transform = [CALayer layer];
transform.frame = CGRectMake(self.bounds.size.width-width/2, 0, 1, 1);
transform.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer addSublayer:transform];
CATransform3D t = CATransform3DMakeTranslation(-width/2, 0, 0);
for (int i=0; i < SIDES ; i++) {
CALayer *rotLayer = [CALayer layer];
rotLayer.anchorPoint = CGPointMake(1, 1);
rotLayer.frame = CGRectMake(0, 0, width, height);
rotLayer.zPosition = -width*0.866;
rotLayer.transform = t;
[transform addSublayer:rotLayer];
t = CATransform3DRotate(t, -M_PI*2/SIDES, 0, 1, 0);
t = CATransform3DTranslate(t, width, 0, 0);
}
count = 0;
layerNum = 0;
}
There are two components to the animation of the transition, but both use sublayer transformation to animate the tube as a whole. The rotation animation and the translation animation are coordinated to effectively rotate the tube like a pencil rolling across the screen, but only to the point that the next side of the tube has rotated enough to be parallel with the view.
ViewTransition/PRPViewTransition.m
- (void) animateLayers {
CABasicAnimation *anim = [CABasicAnimation
animationWithKeyPath:@"sublayerTransform.rotation.y"];
anim.fromValue = [NSNumber numberWithFloat:-M_PI*2/SIDES*count];
anim.toValue = [NSNumber numberWithFloat:-M_PI*2/SIDES*(count+1)];
anim.duration = duration/numLayers;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeBoth;
anim.delegate = self;
[transform addAnimation:anim forKey:@"subRot"];
anim = [CABasicAnimation
animationWithKeyPath:@"sublayerTransform.translation.x"];
anim.fromValue = [NSNumber numberWithFloat:-width*count];
anim.toValue = [NSNumber numberWithFloat:-width*(count+1)];
anim.duration = duration/numLayers*0.98;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeBoth;
[transform addAnimation:anim forKey:@"subTrans"];
count++;
}
The SwitchLayers method coupled with the animationDidStop delegate is the heart of the curl, or roll, effect. At each turn of the tube