Home > Net >  How to make UIKit squish your views so that they can fit into the screen
How to make UIKit squish your views so that they can fit into the screen

Time:06-09

I have five UIView instances constrained using autolayout. When in portrait mode they fit into the screen, but when in landscape mode the screen becomes to small to contain all the views.

I know one way to solve this by creating two constraints and active one in portrait mode and deactivate the other and vice versa in landscape mode like this:

class ViewController : UIViewController{
  var portraitHeight : NSLayoutConstraint!
  var landscapeHeight : NSLayoutConstraint!

  viewDidLoad(){
    super.viewDidLoad()
    portraitHeight = firstView.heightAnchor.constraint(equalToConstant: 88)
    landscapeHeight = firstView.heightAnchor.constraint(equalToConstant: 68)
  }

 override func viewWillLayoutSubviews() {
    let orientation = UIDevice.current.orientation
    if orientation == .portrait
    {
        landscapeHeight.isActive = false
        portraitHeight.isActive = true
        
    }else if orientation == .landscapeLeft || orientation == .landscapeRight
    {
        portraitHeight.isActive = false
        landscapeHeight.isActive = true
       
    }
  }
}

But what I really want is to be able to squish the views when there is not enough space. I tried changing UILayoutPriority but it didn't work.

These are my constraints:

NSLayoutConstraint.activate([
        firstView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        firstView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
        firstView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
        firstView.heightAnchor.constraint(equalToConstant: 88),
        
        secondView.topAnchor.constraint(equalTo: firstView.bottomAnchor),
        secondView.leftAnchor.constraint(equalTo: firstView.leftAnchor),
        secondView.rightAnchor.constraint(equalTo: firstView.rightAnchor),
        secondView.heightAnchor.constraint(equalTo: firstView.heightAnchor),
        
        thirdView.topAnchor.constraint(equalTo: secondView.bottomAnchor),
        thirdView.leftAnchor.constraint(equalTo: firstView.leftAnchor),
        thirdView.rightAnchor.constraint(equalTo: firstView.rightAnchor),
        thirdView.heightAnchor.constraint(equalTo: firstView.heightAnchor),
        
        fourthView.topAnchor.constraint(equalTo: thirdView.bottomAnchor),
        fourthView.leftAnchor.constraint(equalTo: firstView.leftAnchor),
        fourthView.rightAnchor.constraint(equalTo: firstView.rightAnchor),
        fourthView.heightAnchor.constraint(equalTo: firstView.heightAnchor),
        
        fifthView.topAnchor.constraint(equalTo: fourthView.bottomAnchor),
        fifthView.leftAnchor.constraint(equalTo: firstView.leftAnchor),
        fifthView.rightAnchor.constraint(equalTo: firstView.rightAnchor),
        fifthView.heightAnchor.constraint(equalTo: firstView.heightAnchor),

    ])

enter image description here

enter image description here

CodePudding user response:

The key here is indeed to make some constraints have less priority. Namely, for each view, the constraint "height=88" should have a non-required priority, so that it can be broken when the screen height is not enough. On the other hand, you should add another "height <= 88" required constraint.

let view1 = UIView()
view1.backgroundColor = .yellow
let view2 = UIView()
view2.backgroundColor = .blue
let view3 = UIView()
view3.backgroundColor = .brown
let view4 = UIView()
view4.backgroundColor = .cyan
let view5 = UIView()
view5.backgroundColor = .green

let views = [view1, view2, view3, view4, view5]
for view in views {
    view.translatesAutoresizingMaskIntoConstraints = false
    let heightConstraint = view.heightAnchor.constraint(equalToConstant: 88)
    heightConstraint.priority = .defaultHigh
    heightConstraint.isActive = true
    view.heightAnchor.constraint(greaterThanOrEqualToConstant: 88).isActive = true
}

Rather than adding lots of constraints relating the 5 views, I strongly recommend that you use a stack view for this.

let stackView = UIStackView(arrangedSubviews: views)
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)

Since you want the views to all be positioned at the top when there is enough space, you should add these constraints relating the stack view and its super view:

NSLayoutConstraint.activate([
    stackView.topAnchor.constraint(equalTo: view.topAnchor),
    stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
    stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
    stackView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor),
])

Note especially the last one - it's another "<=" constraint. This is what prevents the stack view from exceeding the screen's frame in landscape.

Output:

enter image description here

enter image description here

  • Related