A Swift Perambulation through the World of CATransform3D: Translation, Rotation and Scaling (CALayer, iOS, Xcode)


Whereas a two-dimensional set of co-ordinates has an x-axis and y-axis, a three-dimensional set of co-ordinates as found in the CATransform3D Core Animation Functions has x, y and z axes. A z-axis comes outwards, or upwards, depending on which way you look at it.

This image comes from Wikimedia Commons:



But in Xcode we need to imagine something more like this


and then in our minds rotate it so that the x and y axes are flat against the page.

Translation

If we translate a CALayer or CAShapeLayer along the x-axis using CATransform3DMakeTranslation() it moves to the right (or left if negative) and if it is translated along the y-axis it moves down (or up if negative). If translated along both x and y axes it moves diagonally.



If the layer is translated along the z-axis it moves towards us (or away from us) but doesn't actually change in size, i.e. there is no perspective associated with a layer being translated along the z-axis, it can be thought of as equivalent to the changing of the layer's zPosition, although the zPosition doesn't actually change with the z-axis translation (neither does the anchorPointZ property change). This is because it is a distance moved along the z-axis rather than a relative index.

In Swift the above translations can be written:
let layer = CALayer()
layer.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layer.borderColor = UIColor.blackColor().CGColor
layer.borderWidth = 1
self.view.layer.addSublayer(layer)

let layerX = CALayer()
layerX.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layerX.backgroundColor = UIColor.redColor().CGColor
layerX.transform = CATransform3DMakeTranslation(200.0, 0.0, 0.0)
self.view.layer.addSublayer(layerX)
        
let layerY = CALayer()
layerY.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layerY.backgroundColor = UIColor.blueColor().CGColor
layerY.transform = CATransform3DMakeTranslation(0.0, 200.0, 0.0)
self.view.layer.addSublayer(layerY)
        
let layerXY = CALayer()
layerXY.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layerXY.backgroundColor = UIColor.greenColor().CGColor
layerXY.transform = CATransform3DMakeTranslation(200.0, 200.0, 0.0)
self.view.layer.addSublayer(layerXY)

Rotation

Rotation treats the axes in a different way. A rotation around the x-axis is like watching a backwards flip or a gymnast on a horizontal bar. Rotating around the y-axis is akin to moving around a pole. While a z-axis rotation is like being twisted around.

Viewed in isolation a CALayer that rotates around the the x-axis appears to first shorten towards its centre then lengthen again. A y-axis rotation appears to narrow and then widen. While the z-axis does this (in a 45 degree rotation):

func degree2radian(a:CGFloat)->CGFloat {
      let b = CGFloat(M_PI) * a/180
      return b
}
let layer = CALayer()
layer.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layer.borderColor = UIColor.blackColor().CGColor
layer.borderWidth = 1
self.view.layer.addSublayer(layer)

let layerZrotation = CALayer()
layerZrotation.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 100, height: 100)
layerZrotation.backgroundColor = UIColor.greenColor().CGColor
layerZrotation.transform = CATransform3DMakeRotation(degree2radian(45), 0.0, 0.0, 1.0)
self.view.layer.addSublayer(layerZrotation)
Rotating along more than one axis at once is when things start to appear rotated in three-dimensions. If we rotate 90 degrees and share that 90 degree rotation equally between the z-axis and y-axis, or z-axis and y-axis equally the layer will appear transformed like this (90 degree rotation along y and z axes):

func degree2radian(a:CGFloat)->CGFloat {
    let b = CGFloat(M_PI) * a/180
    return b
}
let layer = CALayer()
layer.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layer.borderColor = UIColor.blackColor().CGColor
layer.borderWidth = 1
self.view.layer.addSublayer(layer)

let layerYZrotation = CALayer()
layerYZrotation.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layerYZrotation.backgroundColor = UIColor.greenColor().CGColor
layerYZrotation.transform = CATransform3DMakeRotation(degree2radian(90), 0.0, 1.0, 1.0)
self.view.layer.addSublayer(layerYZrotation)
It is the result of being pulled in two directions at once. While a three-way (90 degree) rotation across all axes will result in something like this:

func degree2radian(a:CGFloat)->CGFloat {
    let b = CGFloat(M_PI) * a/180
    return b
}
let layer = CALayer()
layer.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layer.borderColor = UIColor.blackColor().CGColor
layer.borderWidth = 1
self.view.layer.addSublayer(layer)

let layerXYZrotation = CALayer()
layerXYZrotation.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layerXYZrotation.backgroundColor = UIColor.greenColor().CGColor
layerXYZrotation.transform = CATransform3DMakeRotation(degree2radian(90), 1.0, 1.0, 1.0)
self.view.layer.addSublayer(layerXYZrotation)
In each instance the anchor point of the layers is set to the default position, which is the centre or (0.5, 0.5). (The x, y and z values are likewise proportional not absolute, as they were with the translation.)

Note: An anchor point of (1.0, 1.0) would result in a rotation around the bottom right corner and (0.0, 0.0) around the top left corner. Other rotation points such as (0.5, 1.0) are also an option.

Scaling

Scaling is different again in its implementation to rotation and translation. The values are a scale factor. So 1.0 means keep at current scale, while 2.0 is 2x scale, etc. In this image the scaling is x: 1.0, y: 2.0

let layer = CALayer()
layer.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layer.borderColor = UIColor.blackColor().CGColor
layer.borderWidth = 1
self.view.layer.addSublayer(layer)

let layerXYscale = CALayer()
layerXYscale.frame = CGRect(x: 20, y: CGRectGetMidX(self.view.frame), width: 200, height: 200)
layerXYscale.backgroundColor = UIColor.greenColor().CGColor
layerXYscale.zPosition = -1
layerXYscale.transform = CATransform3DMakeScale(1.0, 2.0, 0.0)
self.view.layer.addSublayer(layerXYscale)
But here the x value has been changed to 0.5:

As before all scaling occurs relative to the central (default) anchor point of the layer.

Note: a scale factor of zero on the x or y axis would result in a disappearance of the layer since the height or width would be reduced to zero.


Endorse on Coderwall

Comments

  1. Please look at this link :
    http://stackoverflow.com/questions/41054948/how-to-avoid-tilt-flip-on-catransform3d-rotation-in-ios

    ReplyDelete

Post a Comment