Understanding UIView’s transform property (final)
In previous part, we talked about 4 properties of UIView
and relationships between them. This part we cover operations with transform
and how to persist UIView
information. Let’s go!
transform
operations
transform
is type of CGAffineTransform
and it represents a matrix used for affine transformation. Speaking of the matrix and and affine transformation, it is beyond context of this article, but if you wonder and want to learn more, this wiki would be a good start.
In brief, transform
includes 6 matter values a
, b
, c
, d
, x
and y
as the figure below.
a
and d
are for scale, x
and y
are for translation and a
, b
, c
and d
are for rotation. However, usually, we use only scale and rotation of transform
as for translation, we can simply use center
property to position a view in its superview.
When transform
is used for only one transformation, it is straightforward to extract that information, e.g. factor of scale or angle of rotation. In most of cases, however, we use combination of scale and rotation, that makes transformation extraction more tangled. Thanks to mathematics, we already have equations for this and only need to convert to code.
Extract
Right below is snippet for scale extraction
func scaleOf(transform: CGAffineTransform) -> CGPoint {
let xScale = sqrt(transform.a * transform.a + transform.c * transform.c)
let yScale = sqrt(transform.b * transform.b + transform.d * transform.d)
return CGPoint(x: xScale, y: yScale)
}
and for rotation.
func rotationOf(transform: CGAffineTransform) -> CGFloat {
return CGFloat(atan2(transform.b, transform.a))
}
Restore
In reverse, CoreGraphics
provides handy functions to create CGAffineTransform
from transformation information.
view.transform = CGAffineTransformMakeRotation(angle_in_radian) // Create CGAffineTransform of rotation transformation
view.transform = CGAffineTransformMakeScale(xScale, yScale) // Create CGAffineTransform of scale transformation
Because order of transformation matters, in case of combination of scale and rotation, we should persist the whole transform
, i.e., a
, b
, c
, d
, x
and y
values, and use it to restore itself.
view.transform = CGAffineTransform(a: a, b: b, c: c, d: d, tx: x, ty: y);
Persistence
When comes with UIView
information persistence, especially with transform
is applied, note following things:
frame
is invalid so we shouldn’t use it to position or measure view.- Though
frame
is invalid, there is way to calculate actual values and use them for positioning or sizing of view. center
is recommended to position view.bounds.size
is recommended to measure view.
So what information we need to persist? They are position in superview, size and transformation. With transformation, we can persist transform
as a whole or individual one.
Position
The best way is to persist center
, as center
is independent of transform
, it is safe to position view regardless of transform
.
let persistedCenter = view.center
However, in some scenarios, frame.origin
may be useful, then we need calculation on restoration to convert frame.origin
back to center
. It will be there later.
let persistedPosition = frame.origin
Size
Both bounds.size
and frame.size
cannot be used for size persistence, because they do not reflect correct view’s size. bounds.size
is original value before transformation. And frame.size
is size of boundary view, not actual view.
So how? The answer is to use bounds.size
and scale transformation and it is pretty simple.
let scale = scaleOf(view.transform)
let persistedSize = CGSize(width: view.bounds.size.width * scale.x, height: view.bounds.size.height * scale.y)
If we count actual view’s size in persistence, transform
we persist should contain only rotation transformation. This is due of restore phrase later. Otherwise, restoration will get unnecessary calculation, now or later is of your choice. Then I choose now and it also makes more sense to persist real view’s size than its original one.
Transformation
How to persist position and size affects how to do with transformation, actually, only size does. As stated right above, if we persist actual view’s size, rotation is the only thing we need for transformation.
let persistedRotation = rotationOf(view.transform)
How about original view’s size? So we have to persist the whole transform
by using its values.
let a = view.transform.a
let b = view.transform.b
let c = view.transform.c
let d = view.transform.d
With these values, it is easy to restore transform
as talked above.
Restore
Make assumption that we are persisting actual view’s size, its rotation and center
, now it is time to restore the view from that.
let view = UIView(frame: CGRect(x: 0, y: 0, width: persistedSize.width, height: persistedSize.height))
view.transform = CGAffineTransformMakeRotation(persistedRotation)
view.center = persistedCenter
How about frame.origin
for position? Here we are.
let view = UIView(frame: CGRect(x: 0, y: 0, width: persistedSize.width, height: persistedSize.height))
view.transform = CGAffineTransformMakeRotation(persistedRotation)
view.center = CGPoint(x: persistedPosition.x + view.frame.size.width / 2, y: persistedPosition.y + view.frame.size.height / 2)
Conclusion
There are still many more to talk about UIView
, however, with this article, transform
property is not scary anymore, huh? From here, we can keep learning layer.anchorPoint
which is also relevant to transform
, animation with transform
and other 3 properties, etc. Happy learning!