I have a UIViewController
that contains a couple UIView
s, and a UILabel
, and it's presented full screen, embedded in a UINavigationController
. In a non-compact height environment (portrait) I want the boxes to stack on top of each other with the label underneath - that's working as expected. Now when rotating to a compact height environment (landscape) the views of course are repositioned, so the boxes now increase in width and decrease in height. I don't want that behavior. Instead I want to always position the boxes as if the iPhone was in portrait, but the label should rotate as well as the nav bar so that it remains readable in the current orientation. (This means the height of the views will be a bit more constricted in that case due to the nav bar.)
A picture helps to explain a lot better:
So the question is, how can you prevent rotating specific UIView
s while still supporting landscape orientation in the UIViewController
?
I have seen other similar questions, but they use deprecated methods of detecting rotation to a specific device orientation, which is not relevant in iOS 8+. This question is how to accomplish this when thinking in terms of size classes. I believe the solution will use viewWillTransitionToSize
and animateAlongsideTransition
, but what exactly needs to be done is not entirely clear to me.
I am using frame layouts in this case, not auto layout. All views are created programmatically. The boxes are actually subviews of a UIView
subclass that lays out and draws content in the boxes based on the parent view's frame.
In the UIViewController
:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topPosition = self.topLayoutGuide.length
boxesContainer.frame = CGRectMake(20, topPosition, self.view.frame.size.width - 40, (self.view.frame.size.height - topPosition) / 1.5)
boxesContainer.updateLayout()
}
In the box container UIView
:
func updateLayout() {
box1.frame = // something based on self.frame
box2.frame = // ...
}
I worked out a solution. Essentially I detect if the view's width is larger than the height and if so rotate the view that contains the boxes. Do this when first showing the view controller and when the size changes. Then calculations are done to update the frame of this view as necessary. Importantly, the box container view uses the min
of its frame.size.width
and frame.size.height
to lay the boxes.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.optimizeForSize(self.view.bounds.size)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//calculate frame for the box container based on current frame
//...
boxContainer.updateLayout()
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (conext: UIViewControllerTransitionCoordinatorContext) -> Void in
self.optimizeForSize(size)
self.view.setNeedsLayout() //needed for iOS 8
}, completion: nil)
}
func optimizeForSize(size: CGSize) {
if size.width > size.height {
self.boxContainer.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI / 2.0))
} else {
self.colorPicker.transform = CGAffineTransformIdentity
}
}