I have a set of tiles vertically arranged. They are meant to rotate around the X axis. Think of an accordion. I want each tile n+1 to always hang off tile n.
The cyan tile, in the last two screen shots, ought to be attached to the yellow tile. It is not. I do not understand why. Even less so, why when I rotate row0 I get all next 5 tiles rotated (which is what I want) but when I want to "correct" starting from some later tile by rotating in the other direction, I end up with a disconnect between the 3rd and 4th tile.
For reference, here's the setup:
CGRect tileRect = (CGRect) {CGPointZero, {self.bounds.size.width, 30.0f}} ;
CGRect pageRect = tileRect ; pageRect.size.height *= 3 ;
CGPoint baseCenter= (CGPoint) {self.bounds.size.width / 2.0f, 0} ;
CGPoint anchorMidTop= (CGPoint) {0.5f, 0.0f} ;
CGPoint anchorTopLeft= (CGPoint) {0.0f, 0.0f} ;
CGPoint positionNW = (CGPoint) {0.0f, 0.0f} ;
CALayer * (^setupLayerGeometry)(CALayer *, CGRect, CGPoint, CGPoint) =
^(CALayer * layer, CGRect b, CGPoint a, CGPoint p) {
layer.bounds = b ;
layer.anchorPoint = a ;
layer.position = p ;
return layer ;
} ;
CALayer * (^setupColor)(CALayer *, UIColor *) = ^(CALayer * layer, UIColor * color) {
layer.backgroundColor = color.CGColor ;
layer.opacity = 0.850f ;
return layer ;
} ;
CALayer * (^stdLayer)(UIColor *, CGPoint, CGPoint) =
^CALayer * (UIColor * color, CGPoint anchor, CGPoint pos) {
return setupLayerGeometry(
setupColor([CALayer layer], color)
, tileRect
, anchor
, pos) ;
} ;
CATransformLayer * (^transformLayer)(CGRect, CGPoint, CGPoint) =
^CATransformLayer * (CGRect bounds, CGPoint anchor, CGPoint pos) {
return (CATransformLayer *) setupLayerGeometry([CATransformLayer layer]
, bounds
, anchor
, pos) ;
} ;
self.baseLayer = transformLayer(tileRect, anchorTopLeft, baseCenter) ;
CATransform3D initialTransform = self.baseLayer.sublayerTransform ;
initialTransform.m34 = 1.0f / -200.0f ;
self.baseLayer.sublayerTransform = initialTransform ;
[self.layer addSublayer:self.baseLayer] ;
CALayer * (^wrap0) (CALayer *) = ^CALayer * (CALayer * layer) {
CALayer * wrap = transformLayer(tileRect, anchorTopLeft, positionNW) ;
[wrap addSublayer:layer] ;
return wrap ;
} ;
CALayer * (^wrap)(CALayer *) = wrap0 ;
Now I'm creating six tiles, as plain CALayer's.
CALayer * row0 = stdLayer([UIColor redColor], anchorMidTop, (CGPoint) {0, 0}) ;
CALayer * row1 = stdLayer([UIColor blueColor], anchorMidTop, (CGPoint) {0, 30}) ;
CALayer * row2 = stdLayer([UIColor yellowColor], anchorMidTop, (CGPoint) {0, 60}) ;
CALayer * row3 = stdLayer([UIColor cyanColor], anchorMidTop, (CGPoint) {0, 90}) ;
CALayer * row4 = stdLayer([UIColor purpleColor], anchorMidTop, (CGPoint) {0, 120}) ;
CALayer * row5 = stdLayer([UIColor magentaColor], anchorMidTop, (CGPoint) {0, 150}) ;
Now, I'm wrapping each such tile into a parent CATransformLayer, which I am then adding as sublayer of the previous row, with the intention of having each CATransformLayer affect each of its sublayers.
[self.baseLayer addSublayer:wrap(row0)] ;
[row0.superlayer addSublayer:wrap(row1)] ;
[row1.superlayer addSublayer:wrap(row2)] ;
[row2.superlayer addSublayer:wrap(row3)] ;
[row3.superlayer addSublayer:wrap(row4)] ;
[row4.superlayer addSublayer:wrap(row5)] ;
CGFloat angle = M_PI / 10.0f ;
row0.superlayer.sublayerTransform = CATransform3DMakeRotation(-angle, 1, 0, 0);
At this point I get this:

And I thought I had nailed it until I replaced the last line with:
row3.superlayer.sublayerTransform = CATransform3DMakeRotation(-angle, 1, 0, 0);
And here's what I get:

If, instead, I replace the above line with:
row0.superlayer.sublayerTransform = CATransform3DMakeRotation(-angle, 1, 0, 0);
row3.superlayer.sublayerTransform = CATransform3DMakeRotation( angle, 1, 0, 0);
I get this:

I have read and re-read Apple's documentation, paying close attention to this:
Anchor Points Affect Geometric Manipulations
Geometry related manipulations of a layer occur relative to that layer’s anchor point, which you can access using the layer’s anchorPoint property. The impact of the anchor point is most noticeable when manipulating the position or transform properties of the layer. The position property is always specified relative to the layer’s anchor point, and any transformations you apply to the layer occur relative to the anchor point as well.
(emphasis mine)
It is fair to say that I am now utterly confused. Anyone can see/explain what I am doing wrong?
How can I arrange those layers such that my tiles are always adjacent, top to bottom?
Here's a dump of the layer hierarchy:
<CATransformLayer> frame: {{100, 0}, {200, 30}} pos: {100, 0} anchor: {0, 0}
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0, 0}
<CALayer> frame: {{-100, 0}, {200, 30}} pos: {0, 0} anchor: {0.5, 0}
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0, 0}
<CALayer> frame: {{-100, 30}, {200, 30}} pos: {0, 30} anchor: {0.5, 0}
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0, 0}
<CALayer> frame: {{-100, 60}, {200, 30}} pos: {0, 60} anchor: {0.5, 0}
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0, 0}
<CALayer> frame: {{-100, 90}, {200, 30}} pos: {0, 90} anchor: {0.5, 0}
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0, 0}
<CALayer> frame: {{-100, 120}, {200, 30}} pos: {0, 120} anchor: {0.5,
<CATransformLayer> frame: {{0, 0}, {200, 30}} pos: {0, 0} anchor: {0,
<CALayer> frame: {{-100, 150}, {200, 30}} pos: {0, 150} anchor: {0.5, 0}
I got it. Finally!
Here's the code:
The helper blocks:
Now for the baseLayer that is the root
CATransformLayerCreating each row with a
CATransformLayeras its root that also acts as the super layer of the next row.Finally setting the transforms. Since each row is the parent of the next row (thanks to the embedded
CATransformLayer) the angle needed is relative to the immediate parent, thus is independent of the row number a particular tile is displayed at.Now I only have to get rid of those stupid colors, antialias the edges and provide some shadowy gradient layer and watch that perspective which is annoying me (the last row "gray" is too tall) More tweaking with m34 and/or the baseLayer center [anchorPoint/position].
Replacing the angle and coefficients as:
And the perspective to:
We now get: