0

I am creating a UI where we have a deck of cards that you can swipe off the screen.

What I had hoped to be able to do was create a subclass of UIView which would represent each card and then to modify the transform property to move them back (z-axis) and a little up (y-axis) to get the look of a deck of cards.

Reading up on it I found I needed to use a CATransformLayer instead of the normal CALayer in order for the z-axis to not get flattened. I prototyped this by creating a CATransformLayer which I added to the CardDeckView's layer, and then all my cards are added to that CATransformLayer. The code looks a little bit like this:

In init:

// Initialize the CATransformSublayer
_rootTransformLayer = [self constructRootTransformLayer];
[self.layer addSublayer:_rootTransformLayer];

constructRootTransformLayer (the angle method is redundant, was going to angle the deck but later decided not to):

    CATransformLayer* transformLayer = [CATransformLayer layer];
transformLayer.frame = self.bounds;

// Angle the transform layer so we an see all of the cards
CATransform3D rootRotateTransform =  [self transformWithZRotation:0.0];
transformLayer.transform = rootRotateTransform;
return transformLayer;

Then the code to add the cards looks like:

// Set up a CardView as a wrapper for the contentView
RVCardView* cardView = [[RVCardView alloc] initWithContentView:contentView];

cardView.layer.cornerRadius = 6.0;
if (cardView != nil) {
    [_cardArray addObject:cardView];
    //[self addSubview:cardView];
    [_rootTransformLayer addSublayer:cardView.layer];
    [self setNeedsLayout];
}

Note that what I originally wanted was to simply add the RVCardView directly as a subview - I want to preserve touch events which adding just the layer doesn't do. Unfortunately what ends up happening is the following:

Flattened Cards

If I add the cards to the rootTransformLayer I end up with the right look which is:

enter image description here

Note that I tried using the layerClass on the root view (CardDeckView) which looks like this:

+ (Class) layerClass
{
    return [CATransformLayer class];
}

I've confirmed that the root layer type is now CATransformLayer but I still get the flattened look. What else do I need to do in order to prevent the flattening?

nazbot
  • 1,527
  • 2
  • 15
  • 23

1 Answers1

5

When you use views, you see a flat scene because there is no perspective set in place. To make a comparison with 3D graphics, like OpenGL, in order to render a scene you must set the camera matrix, the one that transforms the 3D world into a 2D image.
This is the same: sublayers content are transformed using CATransform3D in 3D space but then, when the parent CALayer displays them, by default it projects them on x and y ignoring the z coordinate.

See Adding Perspective to Your Animations on Apple documentation. This is the code you are missing:

CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / eyePosition; // ...on the z axis

myParentDeckView.layer.sublayerTransform = perspective;

Note that for this, you don't need to use CATransformLayer, a simple CALayer would suffice:

CALayer can handle 3D projections

here is the transformation applied to the subviews in the picture (eyePosition = -0.1):

// (from ViewController-viewDidLoad)
for (UIView *v in self.view.subviews) {
    CGFloat dz = (float)(arc4random() % self.view.subviews.count);
    CATransform3D t = CATransform3DRotate(CATransform3DMakeTranslation(0.f, 0.f, dz),
                                          0.02,
                                          1.0, 0.0, 0.0);
    v.layer.transform = t;
}

The reason for using CATransformLayer is pointed out in this question. CALayer "rasterizes" its transformed sublayers and then applies its own transformation, while CATransformLayer preserves the full hierarchy and draws each sublayer independently; it is useful only if you have more than one level of 3D-transformed sublayers. In your case, the scene tree has only one level: the deck view (which itself has the identity matrix as transformation) and the card views, the children (which are instead moved in the 3D space). So CATransformLayer is superfluous in this case.

Community
  • 1
  • 1
Pietro Saccardi
  • 2,602
  • 34
  • 41
  • Will double check my code and verify this helps. Excellent answer though, really helpful! – nazbot Jul 08 '14 at 20:29
  • @Pietro : Thanks for answer. But what to do if I want frame of views in Perspective mode? I am getting wrong frame into this mode. – girish_pro Dec 27 '16 at 08:33
  • @girish_pro what do you mean with "frame of views"? I'm not familiar with this layout concept. Perhaps you want to alter the [`frame` property](http://stackoverflow.com/questions/5361369/uiview-frame-bounds-and-center) of the layer? – Pietro Saccardi Dec 27 '16 at 11:26
  • @PietroSaccardi Can you please review my question. http://stackoverflow.com/questions/41264038/how-to-make-center-calayer-after-resizing-superview – girish_pro Dec 28 '16 at 08:52
  • If I get the "contents" property of CATransformLayer, will I get a CGImageRef that is a composite of all its sublayers? – James Bush Mar 04 '18 at 21:28
  • @JamesBush according to [Apple’s documentation](https://developer.apple.com/documentation/quartzcore/calayer/1410773-contents) you should get `nil`. I think it would be inefficient to pre-compose and store every layer into a `CGImageRef`. – Pietro Saccardi Mar 04 '18 at 21:44
  • The answer to my question is definitely no; I tried kg put right after I asked. Had it worked, though, it wouldn't be inefficient at all, at least not for the reason you stated. There is no pre-composed state to a CGImage; it is the image data itself. Creating a UIImage or CIImage from it is the only “composing”, if you will, and those are simply wrappers for the image data—as cheap and easy as it gets; they don't copy the data. Any expensive processing associated with a CGImage is long over once you have it; beyond that, everything else has a negligible impact. – James Bush Mar 27 '18 at 16:43
  • @JamesBush Good point, but I'm not 100% sure that every layer has a backing storage (what would be the meaning of [this property](https://developer.apple.com/documentation/quartzcore/calayer/1410905-shouldrasterize?language=objc)?). I could not find conclusive information though. – Pietro Saccardi Mar 27 '18 at 17:04