How to apply multiple transforms in Swift
IosSwiftUiviewTransformCgaffinetransformIos Problem Overview
I would like to apply multiple transforms to a UIView
(or subclass of UIView
), such as translate, rotate, and scale. I know that two transforms can be applied with CGAffineTransformConcat
, but how do I do it if I have three or more transforms?
I have seen these questions:
- https://stackoverflow.com/questions/1111277/applying-multiple-transforms-to-a-uiview-calayer
- https://stackoverflow.com/questions/6047972/using-multiple-cgaffinetransforms-on-text-matrix
but these questions are asking something different, and the given answers just talk about applying two transforms with CGAffineTransformConcat
. Also, they use Objective-C rather than Swift.
Ios Solutions
Solution 1 - Ios
You can apply multiple transforms by stacking them on top of each other.
var t = CGAffineTransform.identity
t = t.translatedBy(x: 100, y: 300)
t = t.rotated(by: CGFloat.pi / 4)
t = t.scaledBy(x: -1, y: 2)
// ... add as many as you want, then apply it to to the view
imageView.transform = t
Or more compactly (but not necessarily as readable):
imageView.transform = CGAffineTransform.identity.translatedBy(x: 100, y: 300).rotated(by: CGFloat.pi / 4).scaledBy(x: -1, y: 2)
This series of transforms produces the image on the right:
Thanks to this answer for teaching me how to do it.
#Notes
-
The order in which you apply the transforms matters. For example, if the transforms were done in the opposite order it would produce the following result.
t = t.scaledBy(x: -1, y: 2) t = t.rotated(by: CGFloat.pi / 4) t = t.translatedBy(x: 100, y: 300)
See also
- CGAffineTransform Reference (docs)
- Transforms (docs)
- Swift: Translating and Rotating a CGContext, A Visual Explanation (iOS/Xcode)
- Demystifying CGAffineTransform
This answer has been tested with Swift 4
Solution 2 - Ios
In Swift 3, these have been replaced by functions on CGAffineTransform itself, which can be chained.
extension CGAffineTransform {
public func translatedBy(x tx: CGFloat, y ty: CGFloat) -> CGAffineTransform
public func scaledBy(x sx: CGFloat, y sy: CGFloat) -> CGAffineTransform
public func rotated(by angle: CGFloat) -> CGAffineTransform
}
so for example
let transform = CGAffineTransform(scaleX: 1.0, y: 3.0).translatedBy(x: 12, y: 9).rotated(by: 17.0)
Solution 3 - Ios
the trick is that
view.transform.translatedBy(x: 100, y: 100)
is NOT changing view.transform
. it just returns the new CGAffineTransform
that you need to ASIGN back to view.transform
.
view.transform = view.transform.translatedBy(x: 100, y: 100)
You can do that as many times as you need or in a sequence
view.transform = view.transform.translatedBy(x: 100, y: 100).rotated(by: CGFloat.pi / 2).scaledBy(x: 2, y: 2)