In portrait mode I have four views on top of one another from the top to the bottom of the view controller (see image).
I then want to change the position of the views relative to one another when the device transitions to landscape (see image two).
I want view 4 to move alongside views 2 and 3 and them all the sit below view 1.
Some of the layout conditions:
- view 1 is attached to the top of the view controller in both landscape and portrait.
- view 4 is attached to the left, right and bottom margins of the view controller in portrait mode.
- Views 2, 3 & 4 are centered horizontally within the portrait view.
What is the best method to achieve the different layouts?
Would the most elegant solution be to make a reference to the constraints in the view controllers code and activate and deactivate them in viewWillTransition? Or is there a way to use vary for traits to achieve this (I can imagine views 2, 3 & 4 being centered horizontally would make this hard to achieve, as well as adding the new constraints for view 4 in landscape mode)?
We used to set up different sets of constraints and activate/deactivate them based upon orientation change. But nowadays can use size classes and "vary for traits".
For example, I start with a simple view and choose a compact width size class and then choose "Vary for traits":
I then add the appropriate constraints and click on "Done varying":
I then choose a "regular width size class" and repeat the process ("Vary for traits", add the constraints, click on "Done varying":
You then end up with a scene that will have a completely different set of constraints active for compact width size classes and regular width size classes. I.e. when I run the app, if the device rotates, the new constraints are activated:
For more information, see WWDC 2016 videos on adaptive layouts:
- https://developer.apple.com/videos/play/wwdc2016/222
- https://developer.apple.com/videos/play/wwdc2016/233
I use arrays of constraints and activate/deactivate according to orientation.
var p = [NSLayoutConstraint]()
var l = [NSLayoutConstraint]()
var initialOrientation = true
var isInPortrait = false
override func viewDidLoad() {
super.viewDidLoad()
// add any subviews here
view.turnOffAutoResizing()
// add constraints here....
// common constraints you can set their isActive = true
// otherwise, add in to portrait(p) and landscape(l) arrays
}
override func viewWillLayoutSubviews() {
super.viewDidLayoutSubviews()
if initialOrientation {
initialOrientation = false
if view.frame.width > view.frame.height {
isInPortrait = false
} else {
isInPortrait = true
}
view.setOrientation(p, l)
} else {
if view.orientationHasChanged(&isInPortrait) {
view.setOrientation(p, l)
}
}
}
extension UIView {
public func turnOffAutoResizing() {
self.translatesAutoresizingMaskIntoConstraints = false
for view in self.subviews as [UIView] {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
public func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
if self.frame.width > self.frame.height {
if isInPortrait {
isInPortrait = false
return true
}
} else {
if !isInPortrait {
isInPortrait = true
return true
}
}
return false
}
public func setOrientation(_ p:[NSLayoutConstraint], _ l:[NSLayoutConstraint]) {
NSLayoutConstraint.deactivate(l)
NSLayoutConstraint.deactivate(p)
if self.bounds.width > self.bounds.height {
NSLayoutConstraint.activate(l)
} else {
NSLayoutConstraint.activate(p)
}
}
}
My needs to distinguish portrait or landscape require using something other than size classes, as my app is universal and iPad (unless using split or slide out view) is always normal size. Also, you may get use viewWillTransistion(toSize:)
or viewDidLoadSubviews()
instead of 'viewWillLoadSubviews()` - but always test, as these may be executed more than once on an orientation change!