ref:
http://stackoverflow.com/questions/3831867/trying-to-make-a-card-from-calayers-that-can-flip-over
http://www.binpress.com/tutorial/objectivec-lesson-13-keyvalue-coding/79
Setting up the Layers
Basically, we have two layers. One if the front with some text. The other is ‘back’ layer with some text, in which we place underneath the front.
However, you rotate the ‘back’ 180 degrees so that back’s text is facing the back. That way, when we rotate the whole thing, we can see the text of front and back.
But first, let’s make a container layer to contain the front and back layers. Notice that its a CATransformLayer. This a a layer that’s just for containing other layers (it can’t have things like backgroundColor or borderWidth). It does however maintain the true 3D relationship of its child layers (i.e., it doesn’t flatten them like a regular CALayer). So you can make two layers, flip one and offset its zPosition just a hair and put them both in a CATransformLayer and now you can flip this parent layer around and the child layers stay locked together and always render properly.
1 2 3 |
@interface ViewController () @property(nonatomic, strong) CATransformLayer * myLayer; @end |
method that creates and returns the ‘back’ CALayer
Notice at the end that we flip this back layer 180 so that it faces the other way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
- (CALayer *)createBack { CALayer *back = [[CALayer alloc] init]; back.bounds = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); //back.anchorPoint = CGPointMake(0, 0); back.backgroundColor = [[UIColor blueColor] CGColor]; back.contentsGravity = kCAGravityResize; back.masksToBounds = YES; back.edgeAntialiasingMask = 0; back.doubleSided = NO; back.frame = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); back.borderWidth = 4 * (200.0f/150); back.borderColor = [[UIColor grayColor] CGColor];; back.cornerRadius = 8 * (200.0f/150); CATextLayer *bTextLayer = [CATextLayer layer]; UIFont * uiFont = [UIFont fontWithName:@"AmericanTypewriter" size:12]; bTextLayer.font = CGFontCreateWithFontName((CFStringRef)uiFont.fontName); bTextLayer.bounds = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); //mid-point bTextLayer.position = CGPointMake(100.0f, 100.0f); bTextLayer.string = @"Uppercut!"; bTextLayer.backgroundColor = [UIColor redColor].CGColor; bTextLayer.contentsScale = UIScreen.mainScreen.scale; [back addSublayer:bTextLayer]; //transform so that the back is facing the back back.transform = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f); return back; } |
Now create the front layer. We leave front layer as is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
-(CALayer*)createFront { CALayer *front = [[CALayer alloc] init]; front.bounds = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); front.anchorPoint= CGPointMake(0, 0); front.edgeAntialiasingMask = 0; front.backgroundColor = [[UIColor purpleColor] CGColor]; front.doubleSided = NO; front.frame = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); front.borderWidth = 4 * (200.0f/150); front.borderColor = [[UIColor grayColor] CGColor];; front.cornerRadius = 8 * (200.0f/150); front.contentsScale = UIScreen.mainScreen.scale; //text for front layer CATextLayer *fTextLayer = [CATextLayer layer]; UIFont * uiFont = [UIFont fontWithName:@"AmericanTypewriter" size:12]; fTextLayer.font = CGFontCreateWithFontName((CFStringRef)uiFont.fontName); fTextLayer.bounds = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f); //midpoint according to parent layer fTextLayer.position = CGPointMake(100.0f, 100.0f); //fTextLayer.string = [NSString stringWithFormat:@"f %lu", (long)self.tag]; fTextLayer.string = @"TIGER!"; fTextLayer.backgroundColor = [UIColor yellowColor].CGColor; fTextLayer.contentsScale = UIScreen.mainScreen.scale; [front addSublayer:fTextLayer]; return front; } |
The basic set up. First we create the container layer. Then we add the back first, and then the front on top of that. Then, we have the self layer add this container layer. After that, we have a nice card where when we rotate this container layer, we can see the front and back as represented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void)viewDidLoad { [super viewDidLoad]; //create sublayer self.myLayer = [CATransformLayer layer]; self.myLayer.frame = CGRectMake(50.0f, 250.0f, kCardWidth, kCardWidth); self.myLayer.backgroundColor = [UIColor clearColor].CGColor; [self.myLayer addSublayer:[self createBack]]; [self.myLayer addSublayer:[self createFront]]; //add it to our view [self.view.layer addSublayer:self.myLayer]; |
Create the Animation
valueForKeyPath or Key-Value Coding.
Accessing ivars is usually done through accessor methods or dot-notation. But as a means of indirection, there is another way—Key-Value Coding, or KVC. This is a means of setting and getting the values by using strings—specifically, NSString, and its corresponding methods, including the very useful stringWithFormat:.
The key is in fact the same name as your variable. It follows a specific sequence of steps:
1) First, it looks for a getter method with the same name. In the above example, it looks for the -name or -isName (for boolean values) methods; if they are found, it invokes that method and returns that result.
2) If the getter methods are not found, the method looks for an instance variable by that name: name or _name.
Hence we get the number for rotating around y. Then we say that we want the animation
1 2 3 4 5 6 7 8 9 |
NSNumber * rotationAtStart = [self.myLayer valueForKeyPath:@"transform.rotation.y"]; // 1) CREATE ANIMATION CABasicAnimation *animationRotateRight = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"]; animationRotateRight.additive = YES; animationRotateRight.removedOnCompletion = NO; animationRotateRight.duration = 1.0f; animationRotateRight.fromValue = rotationAtStart; animationRotateRight.toValue = [NSNumber numberWithFloat:([rotationAtStart floatValue] + M_PI)]; |
Have the container layer add the Animation
We add the animation to our CATransformLayer and give it the name rotateYright.
1 2 3 4 |
// 2) LAYER ADD ANIMATION [self.myLayer addAnimation:animationRotateRight forKey:@"rotateYright"]; self.myLayer.speed = 0.0f; |
Create the slider and add it to our self.view
Notice the minimum is set to 0 and max to 1. This is so that we can give decimal values from 0 to 1 and insert it into iVar timeOffset for our CATransformLayer container.
1 2 3 4 5 6 7 8 9 10 11 |
// 3) UIVIEW CREATE OUR UISLIDER CGRect frame = CGRectMake(40.0, 600.0, 200.0, 10.0); UISlider *slider = [[UISlider alloc] initWithFrame:frame]; [slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged]; [slider setBackgroundColor:[UIColor clearColor]]; slider.minimumValue = 0.0; slider.maximumValue = 1.0; slider.continuous = YES; slider.value = 0.0; [self.view addSubview:slider]; |
Implement the slider action method
then you set the slider value in decimal between 0 and 1 and feed it to CATransformLayer’s timeOffset. Hence, you can now control the animation of flipping a card around the Y axis.
1 2 3 4 5 6 7 8 |
-(void)sliderAction:(id)ctrl { UISlider * slider = (UISlider*)ctrl; // 4) LAYER TIME OFFSET self.myLayer.timeOffset = slider.value; NSLog(@"value: %f", slider.value); } |