views:

122

answers:

2

I've been trying to develop a CALayer based "CardView" object that has two layers that face away from each other to present the two sides of a card. I've been working with things like the doublesided property of the CALayer and find the results to be confusing. My base class is CALayer and I'm adding two sublayers to it, one with a M_PI/2 transform and both with doublesided = NO but as it stands, I get the front showing no matter how I rotate the card and the back doesn't show at all. If I don't create the front, the back shows through both sides and the text is in one corner, not 48 point and blurry.

Here's a link to a screenshot showing the card just finishing a full revolution in a UIViewController's view. You're seeing the back of the front. This should be invisible, and the back should be showing... I think...

https://files.me.com/gazelips/lj2aqo


//  CardView.h

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

#define kCardWidth 100.0f
#define kCardHeight 150.0f

@interface CardView : CALayer {
    CALayer *cardFront, *cardBack;
}

@property (nonatomic, retain) CALayer *cardFront, *cardBack;

- (id)initWithPosition:(CGPoint)point;
- (void)addPerspective;
- (CALayer *)createFront;
- (CALayer *)createBack;

@end

//  CardView.m

#import "CardView.h"

@implementation CardView

@synthesize cardFront, cardBack;

static CATransform3D kPerspectiveTransform;

- (id)initWithPosition:(CGPoint)point {
    NSLog(@"--initWithPosition:CardView");
    self = [super init];
    if (self != nil) {
        [self addPerspective];
        self.bounds = CGRectMake(0, 0, kCardWidth, kCardHeight);
        self.position = point;
        self.edgeAntialiasingMask = 0;
        self.backgroundColor = [[UIColor clearColor] CGColor];
        self.borderColor = [[UIColor blackColor] CGColor];
        self.borderWidth = 0.5;
        self.doubleSided = YES;

        cardBack = [self createBack];
        [self addSublayer: cardBack];

        cardFront = [self createFront];
        [self addSublayer: cardFront];
    }
    return self;
}

- (void)addPerspective {
    NSLog(@"--prepare:CardView");
    kPerspectiveTransform = CATransform3DIdentity;
    kPerspectiveTransform.m34 = -1.0/800.0;
    self.transform = kPerspectiveTransform;
}   

- (CALayer *)createFront {
    NSLog(@"--createFront:CardView");
    CALayer *front = [[CALayer alloc] init];
    front.bounds = CGRectMake(0.0f, 0.0f, kCardWidth, kCardHeight);
    front.position = CGPointMake(kCardWidth / 2, kCardHeight / 2);
    front.edgeAntialiasingMask = 0;
    front.backgroundColor = [[UIColor whiteColor] CGColor];
    front.cornerRadius = 8 * (kCardHeight/150);
    front.borderWidth = 1;
    front.borderColor = [[UIColor grayColor] CGColor];
    front.doubleSided = NO;

    return [front autorelease];
}

- (CALayer *)createBack {
    NSLog(@"--createBack:CardView");
    CALayer *back = [[CALayer alloc] init];
    back.bounds = CGRectMake(0.0f, 0.0f, kCardWidth, kCardHeight);
    back.position = CGPointMake(kCardWidth / 2, kCardHeight / 2);
    back.backgroundColor = [[UIColor blueColor] CGColor];
    back.contentsGravity = kCAGravityResize;
    back.masksToBounds = YES;
    back.borderWidth = 4 * (kCardHeight/150);
    back.borderColor = [[UIColor grayColor] CGColor];;
    back.cornerRadius = 8 * (kCardHeight/150);
    back.edgeAntialiasingMask = 0;
    back.doubleSided = NO;

    CATextLayer *textLayer = [CATextLayer layer];
    textLayer.font = [UIFont boldSystemFontOfSize:48];
    textLayer.bounds = CGRectMake(0.0f, 0.0f, kCardWidth, kCardHeight);
    textLayer.position = CGPointMake(50.0f, 75.0f);
    textLayer.string = @"Steve";
    [back addSublayer:textLayer];

    back.transform = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
    return [back autorelease];
}

@end

+1  A: 

There is a much easier way to do this, as long as you're only trying to do a normal 'flip'.

Look into the UIView animation system, and there is a 'transitionWithView'. You can apply a flip animation there, and it will provide the same effect, with near no work.

Joshua Weinberg
@joshua - Thanks! I've already got a version (this is #6 I think) that uses a UIView and I can flip it around all 3 axes while I'm moving it. I just made a UIView and added its layer to my ViewController's. But I couldn't get the double sided thing figured out.
Steve
+1  A: 

The trick ended up being the use of 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 six ways from Sunday and the child layers stay locked together and always render properly. Pretty sweet and low overhead!

Steve