Skip to content

Instantly share code, notes, and snippets.

@ninjaprox
Last active March 2, 2018 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ninjaprox/b48583ece232ff5417bce5ee978e34da to your computer and use it in GitHub Desktop.
Save ninjaprox/b48583ece232ff5417bce5ee978e34da to your computer and use it in GitHub Desktop.
Understanding UIView's tranform property
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
// MARK: - Helpers
extension UIView {
func addBorderWith(color: UIColor, width: CGFloat, alpha: CGFloat = 1) {
self.layer.borderColor = color.colorWithAlphaComponent(alpha).CGColor
self.layer.borderWidth = width
}
}
// Reference: http://www.informit.com/articles/article.aspx?p=1951182
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)
}
func rotationOf(transform: CGAffineTransform) -> CGFloat {
return CGFloat(atan2f(Float(transform.b), Float(transform.a)))
}
// MARK: - Setup
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
containerView.backgroundColor = UIColor.grayColor()
XCPlaygroundPage.currentPage.liveView = containerView
let originalView = UIView(frame: CGRect(x: 150, y: 150, width: 150, height: 150))
originalView.backgroundColor = UIColor.greenColor()
containerView.addSubview(originalView)
// MARK: - Experiment
let transformedView = UIView(frame: originalView.frame)
transformedView.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.5)
transformedView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI) / 12)
containerView.addSubview(transformedView)
/**
When `transform` is applied, view's `frame` is invalid. It is now actually of boundary view.
*/
assert(!CGRectEqualToRect(originalView.frame, transformedView.frame))
let boundaryView = UIView(frame: transformedView.frame)
boundaryView.addBorderWith(UIColor.redColor(), width: 1)
containerView.addSubview(boundaryView)
/**
However, either view's `bounds` or `center` keeps the same.
*/
assert(CGRectEqualToRect(originalView.bounds, transformedView.bounds))
assert(CGPointEqualToPoint(originalView.center, transformedView.center))
/**
To persist transformed view's location, size, it is recommended to persist its `center`, `size` of `bounds` and `transform` instead `origin` of `frame`.
However, if the data is `origin` of `frame`, there is way to display it properly.
Solution:
- Init view with its size (`size` of `bounds`) regarless its origin (`origin` of `frame`).
- Set `transform`.
- Calculate `center` based on `frame` after `tranform` applied.
*/
let clonedTransformedView = UIView(frame: CGRect(x: 0, y: 0, width: transformedView.bounds.width, height: transformedView.bounds.height))
let persistedOrigin = transformedView.frame.origin
let persistedSize = transformedView.bounds.size
clonedTransformedView.addBorderWith(UIColor.blueColor(), width: 2)
clonedTransformedView.transform = transformedView.transform
clonedTransformedView.center = CGPoint(x: persistedOrigin.x + clonedTransformedView.frame.width / 2, y: persistedOrigin.y + clonedTransformedView.frame.height / 2)
containerView.addSubview(clonedTransformedView)
/**
Effect of `tranform` to child views.
*/
let childView1 = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
childView1.backgroundColor = UIColor.brownColor().colorWithAlphaComponent(0.5)
transformedView.addSubview(childView1)
transformedView.transform = CGAffineTransformScale(transformedView.transform, 2, 2) // Comment this line to see the actual position and size before scale transform
/**
There is difference between changing `frame` size and `bounds` size of child views.
Changing `frame` size keeps its `frame` origin, whereas `bounds` size does not.
Changing `bounds` size grows or shrinks the view relative to its center point.
*/
let scale = scaleOf(transformedView.transform)
let childView2 = UIView(frame: childView1.frame)
childView2.frame.size = CGSize(width: childView2.bounds.width / scale.x, height: childView2.bounds.height / scale.y)
childView2.addBorderWith(UIColor.brownColor(), width: 2)
transformedView.addSubview(childView2)
let childView3 = UIView(frame: childView1.frame)
childView3.contentMode = .TopLeft
childView3.bounds.size = CGSize(width: childView3.bounds.width / scale.x, height: childView3.bounds.height / scale.y)
childView3.addBorderWith(UIColor.brownColor(), width: 2)
transformedView.addSubview(childView3)
assert(!CGRectEqualToRect(childView2.frame, childView3.frame))
assert(CGRectEqualToRect(childView2.bounds, childView3.bounds))
/**
The above is correct even when `transform` is identity transform.
*/
let childView4 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let childView5 = UIView(frame: childView4.frame);
childView4.backgroundColor = UIColor.redColor()
childView5.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.5)
childView4.bounds.size = CGSize(width: 50, height: 50)
originalView.addSubview(childView4)
originalView.addSubview(childView5)
assert(CGPointEqualToPoint(childView4.center, childView5.center))
/**
Re-create transformed view (with scale and rotation) by using real size after scale and rotation.
Therefore, `transform` will be rotation only transformation instead of both scale and rotation
This results in another way to persist view's information. Persist after scale size of the view, center and rotation only (not scale-rotation transformation as ealier)
*/
let clonedTransformedView2 = UIView(frame: CGRect(x: 0, y: 0, width: transformedView.bounds.width * scale.x, height: transformedView.bounds.height * scale.y))
let rotation = rotationOf(transformedView.transform)
clonedTransformedView2.addBorderWith(UIColor.redColor(), width: 2)
clonedTransformedView2.transform = CGAffineTransformMakeRotation(rotation)
clonedTransformedView2.center = transformedView.center
containerView.addSubview(clonedTransformedView2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment