Swift: Notes on the relative positioning and transformation of layers and sublayers (CALayer, frame, bounds, transform, position, Xcode)


When a CALayer is created using CALayer() it is constructed with its name as nil, and its frame values, bounds values and position values set to zero.
let myLayer = CALayer()
self.view.layer.addSublayer(myLayer)
And this remains true after an unsized frame is added to a layer as a sublayer.

Sizing and positioning a layer frame

myLayer.frame = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
Once frame values are set, the bounds and position properties also change relative to this. The height, width and position of the frame and bounds are at this stage identical and the position x,y values are half the width and half the height respectively (because the layer is by default positioned on its centre, that is its "anchorPoint"). Here are a list of the set values based on the above code:
  • name = nil
  • Frame X: 0.0 Frame Y: 0.0
  • Frame Height: 200.0 Frame Width: 200.0
  • Bounds X: 0.0 Bounds Y: 0.0
  • Bounds Height: 200.0 Bounds Width: 200.0
  • Position X: 100.0 Position Y: 100.0
Setting the bounds with the same values:
myLayer.bounds = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
a different result occurs:
  • name = nil
  • Frame X: -100.0 Frame Y: -100.0
  • Frame Height: 200.0 Frame Width: 200.0
  • Bounds X: 0.0 Bounds Y: 0.0
  • Bounds Height: 200.0 Bounds Width: 200.0
  • Position X: 0.0 Position Y: 0.0
The bounds x,y position stays the same but the position values shift and so do the frame x,y (origin) values.

Transforming a layer

Next let's add a transform to the layer and see what happens:
myLayer.frame = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
myLayer.transform = CATransform3DMakeTranslation(200,200,0)
The results are as follows:
  • name = nil
  • Frame X: 200.0 Frame Y: 200.0
  • Frame Height: 200.0 Frame Width: 200.0
  • Bounds X: 0.0 Bounds Y: 0.0
  • Bounds Height: 200.0 Bounds Width: 200.0
  • Position X: 100.0 Position Y: 100.0
The frame has shifted but the bounds and position remain fixed (note: I set the frame here, not the bounds, so comparison should be to the first set of results above.)

A splash of colour

Adding a background colour, it makes it possible for the layer to be seen visibly on the screen:
myLayer.backgroundColor = UIColor.blueColor().CGColor
And this enables us to see that when a translation is applied the visible layer moves and that it is the frame properties that determine the position of what we see.

Positioning a layer

Whereas the position does not change with a transform, if the position is changed then so too is the frame:
myLayer.frame = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
myLayer.position = CGPoint(x:300,y:300)
myLayer.backgroundColor = UIColor.blueColor().CGColor
As is shown in these results:
  • name = nil
  • Frame X: 100.0 Frame Y: 100.0
  • Frame Height: 200.0 Frame Width: 200.0
  • Bounds X: 0.0 Bounds Y: 0.0
  • Bounds Height: 200.0 Bounds Width: 200.0
  • Position X: 200.0 Position Y: 200.0

Adding a sublayer

In the following code:
let myLayer = CALayer()
self.view.layer.addSublayer(myLayer)
myLayer.frame = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
myLayer.position = CGPoint(x:200,y:200)
myLayer.backgroundColor = UIColor.blueColor().CGColor
let mySubLayer = CALayer()
mySubLayer.frame = CGRect(x:CGRectGetMinX(myLayer.bounds),y:CGRectGetMinY(myLayer.bounds),width: CGRectGetWidth(myLayer.frame)/2,height: CGRectGetHeight(myLayer.frame)/2)
mySubLayer.backgroundColor = UIColor.yellowColor().CGColor
myLayer.addSublayer(mySubLayer)
A yellow square is placed in the top left corner of the blue square. And the sublayer has the following properties:
  • name = nil
  • Frame X: 0.0 Frame Y: 0.0
  • Frame Height: 100.0 Frame Width: 100.0
  • Bounds X: 0.0 Bounds Y: 0.0
  • Bounds Height: 100.0 Bounds Width: 100.0
  • Position X: 50.0 Position Y: 50.0

Why you shouldn't position a sublayer relative to a superlayer's frame

However, if the frame uses as its reference point the frame of its superlayer, this is the result:
mySubLayer.frame = CGRect(x:CGRectGetMinX(myLayer.frame),y:CGRectGetMinY(myLayer.frame),width: CGRectGetWidth(myLayer.frame)/2,height: CGRectGetHeight(myLayer.frame)/2)


The sublayer will appear in the bottom right layer. And if we changed the position to:
myLayer.position = CGPoint(x:300,y:300)


The sublayer wouldn't appear at all. This is because the frame properties of a sublayer are relative to the bounds of its superlayer, not a fixed position. And the only reason that the sublayer was visible the first time was because the x,y of the frame happened to be no larger than the height and width of the layer.

Rotation of a layer

If we now rotate the layer, the sublayer rotates with it:
 let myLayer = CALayer()
self.view.layer.addSublayer(myLayer)
 
myLayer.frame = CGRect(x:CGRectGetMinX(self.view.bounds),y:CGRectGetMinY(self.view.bounds),width: 200,height: 200)
myLayer.position = CGPoint(x:200,y:200)
myLayer.backgroundColor = UIColor.blueColor().CGColor
let mySubLayer = CALayer()
mySubLayer.frame = CGRect(x:CGRectGetMinX(myLayer.bounds),y:CGRectGetMinY(myLayer.bounds),width: CGRectGetWidth(myLayer.frame)/2,height: CGRectGetHeight(myLayer.frame)/2)
mySubLayer.backgroundColor = UIColor.yellowColor().CGColor
myLayer.addSublayer(mySubLayer)
layerTests(mySubLayer)
func degree2radian(a:CGFloat)->CGFloat {
     let b = CGFloat(M_PI) * a/180
     return b
}
myLayer.transform = CATransform3DMakeRotation(degree2radian(60),0,0,1)


Relative transformation of a sublayer

And if the sublayer is transformed while the superlayer is transformed then all transformations will be made relative to the superlayer's transformation. In the following a simple down movement along the y axis will seen on the screen as a diagonal movement because it has happened within the bounds of the superlayer.
mySubLayer.transform = CATransform3DMakeTranslation(0,100,0)


Endorse on Coderwall

Comments

Post a Comment