I managed to obtain this, check:
rotation working as desired
Note: The green and the red views are subviews of the controller's view. The blue view is subview of the red view.
Idea
According to https://developer.apple.com/library/archive/qa/qa1890/_index.html, we need to apply an inverse rotation to the view when it is transitioning to a new size.
In addition to that, we need to adjust the constraints after rotation (landscape/portrait).
Implementation
class MyViewController: UIViewController {
var viewThatShouldNotRotate = UIView()
var view2 = UIView()
var insiderView = UIView()
var portraitConstraints: [NSLayoutConstraint]!
var landscapeConstraints: [NSLayoutConstraint]!
override func viewDidLoad() {
super.viewDidLoad()
viewThatShouldNotRotate.backgroundColor = .red
view2.backgroundColor = .green
insiderView.backgroundColor = .blue
view.addSubview(viewThatShouldNotRotate)
view.addSubview(view2)
viewThatShouldNotRotate.addSubview(insiderView)
portraitConstraints = createConstraintsForPortrait()
landscapeConstraints = createConstraintsForLandscape()
}
func createConstraintsForLandscape() -> [NSLayoutConstraint] {
return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
viewThatShouldNotRotate.autoMatch(.height, to: .width, of: view)
viewThatShouldNotRotate.autoMatch(.width, to: .height, of: view)
viewThatShouldNotRotate.autoCenterInSuperview()
view2.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(), excludingEdge: .top)
view2.autoSetDimension(.height, toSize: 100)
insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
insiderView.autoSetDimension(.height, toSize: 100)
}
}
func createConstraintsForPortrait() -> [NSLayoutConstraint] {
return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
viewThatShouldNotRotate.autoMatch(.height, to: .height, of: view)
viewThatShouldNotRotate.autoMatch(.width, to: .width, of: view)
viewThatShouldNotRotate.autoCenterInSuperview()
view2.autoPinEdges(toSuperviewMarginsExcludingEdge: .top)
view2.autoSetDimension(.height, toSize: 100)
insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
insiderView.autoSetDimension(.height, toSize: 100)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if view.bounds.size.width > view.bounds.size.height {
// landscape
portraitConstraints.forEach {$0.autoRemove()}
landscapeConstraints.forEach { $0.autoInstall() }
} else {
landscapeConstraints.forEach {$0.autoRemove()}
portraitConstraints.forEach { $0.autoInstall() }
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let viewToRotate: UIView = viewThatShouldNotRotate
coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
let deltaTransform = coordinator.targetTransform
let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))
var currentRotation = (viewToRotate.layer.value(forKeyPath: "transform.rotation.z") as! NSNumber).floatValue
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
currentRotation = currentRotation + (-1 * Float(deltaAngle)) + 0.0001
viewToRotate.layer.setValue(currentRotation, forKeyPath:"transform.rotation.z")
}) { (UIViewControllerTransitionCoordinatorContext) -> Void in
var currentTransform = viewToRotate.transform;
currentTransform.a = round(currentTransform.a);
currentTransform.b = round(currentTransform.b);
currentTransform.c = round(currentTransform.c);
currentTransform.d = round(currentTransform.d);
viewToRotate.transform = currentTransform;
}
}
}
size
that was being passed in. – Iorio