I am practicing auto-layout programmatically. I want to put a UIView centered in the controller whose width will be 4/5 in portrait mode but when it will go to the landscape mode, I need the height to be of 4/5 of the super view's height, rather than the width.
Something like -
So, I am deactivating and then activating the constrains required depending on the orientation but when I change rotation, it gives me conflict as if it didn't deactivated the ones, I specified to be deactivated. Here is my full code. As It is storyboard independent, one can just assign the view controller class to a view controlller and see the effect.
class MyViewController: UIViewController {
var widthSizeClass = UIUserInterfaceSizeClass.unspecified
var centeredView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.systemGreen
return view
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.addSubview(centeredView)
centeredView.translatesAutoresizingMaskIntoConstraints = false
}
override func viewWillLayoutSubviews(){
super.viewWillLayoutSubviews()
widthSizeClass = self.traitCollection.horizontalSizeClass
addConstrainsToCenterView()
}
func addConstrainsToCenterView() {
centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
let compactWidthAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
let compactHeightAnchor = centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor)
let regularHeightAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
if widthSizeClass == .compact{
NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
NSLayoutConstraint.activate([compactWidthAnchor, compactHeightAnchor])
}
else{
NSLayoutConstraint.deactivate([compactWidthAnchor, compactHeightAnchor])
NSLayoutConstraint.activate([regularWidthAnchor, regularHeightAnchor])
}
}
}
Can anyone please help me detect my flaw.
Couple issues...
1 - many iPhone models only have wC hR
(portrait) and wC hC
(landscape) size classes. So, if you're checking for the .horizontalSizeClass
on those devices it will always be .compact
. You likely want to be checking the .verticalSizeClass
2 - the way you have your code, you are creating NEW constraints every time you call addConstrainsToCenterView()
. You're not activating / deactivating existing constraints.
Take a look at this:
class MyViewController: UIViewController {
var heightSizeClass = UIUserInterfaceSizeClass.unspecified
var centeredView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.systemGreen
return view
}()
// constraints to activate/deactivate
var compactAnchor: NSLayoutConstraint!
var regularAnchor: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(centeredView)
centeredView.translatesAutoresizingMaskIntoConstraints = false
// centeredView is Always centerX and centerY
centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
// for a square (1:1 ratio) view, it doesn't matter whether we set
// height == width
// or
// width == height
// so we can set this Active all the time
centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor).isActive = true
// create constraints to activate / deactivate
// for regular height, set the width to 4/5ths the width of the view
regularAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
// for compact height, set the height to 4/5ths the height of the view
compactAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// use .verticalSizeClass
heightSizeClass = self.traitCollection.verticalSizeClass
updateCenterViewConstraints()
}
func updateCenterViewConstraints() {
if heightSizeClass == .compact {
// if height is compact
regularAnchor.isActive = false
compactAnchor.isActive = true
}
else{
// height is regular
compactAnchor.isActive = false
regularAnchor.isActive = true
}
}
}
With that approach, we create two vars for the constraints we want to activate/deactivate:
// constraints to activate/deactivate
var compactAnchor: NSLayoutConstraint!
var regularAnchor: NSLayoutConstraint!
Then, in viewDidLoad()
, we add centeredView
to the view, set its "non-changing" constraints - centerX, centerY, aspect-ratio - and create the two activate/deactivate constraints.
When we change the size class, we only have to deal with the two var
constraints.