How to programmatically set constraints when View sizes are different?

922 Views Asked by At

Using Xcode 10, iOS 11.4+, Swift 4

I have a series of UIViewControllers that I am navigating through, each of which is being placed into a ContainerView.

enter image description here

Based on which VC is pushed, I am hiding/showing the Top or Bottom views (shown in gray) in the Main Controller while the ContainerView is always in the middle (light blue).

What I would like to do is set the constraints so that the ContainerView is appropriately sized when the Top or Bottom views are shown/hidden.

For example, when the "Main Menu" is shown, the ContainerView should fill the entire Main Container view (Top and Bottom views will be hidden)

When "Item1" is shown, the Top view will be shown and the Bottom view hidden. Therefore, ContainerView should fill the Main Container view left, right, and bottom, but the ContainerView Top should be constrained to the Top view bottom.

When "Item5" is shown, the Top and Bottom views will also be shown. Therefore, ContainerView should fill Main Container view left, right, and be constrained to the Top view bottom, and the Bottom view top (as shown in the Main Container)

I've tried using code like this to fill the entire Main view:

NSLayoutConstraint.activate([
   ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0),
   ContainerView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0),
   ContainerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
   ContainerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
])

However, the ContainerView never changes, and Xcode gives me a lot of warnings like:

[LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want.

Here's another screenshot from the Storyboard: enter image description here

Here's a link to download my sample project: https://gitlab.com/whoit/containerviews

How can I correctly modify the constraints to accomplish this?

2

There are 2 best solutions below

3
On BEST ANSWER

Issues

1) You're adding new constraints all the time, as this lines create a new constraints, each time they're getting called:

ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)

2) In the beginning, the MainContainerView is not constraint at all

Solution

I would suggest following:

  • add those four constraints in the storyboard instead
  • create IBOutlets for the height-constraints of your top and bottom views.
  • set the constants of those to 0 (when hiding them)
  • (optional) for sure you can still fade them in/out by setting their alpha as well, and then add the height constant to the completion block.

Note: Hidden views (alpha = 0 or isHidden = true) are still taken into account for layouting. That means, your constraints are still valid, when the views are hidden. But to make them look like hidden for the layouting as well, you'll then have to set theirs height constants to 0.

Code

@objc func hideControlViews(_ notification: Notification){
    let userInfo = notification.userInfo! as! [String : Bool]
    //print("Got top view notification: \(String(describing: userInfo["hide"]))")
    if (userInfo["hidetop"]!) {
        self.topViewHeightConstraint.constant = 0
    } else {
        self.topViewHeightConstraint.constant = 64
    }
    if (userInfo["hidebottom"]!) {
        self.bottomViewHeightConstraint.constant = 0
    } else {
        self.bottomViewHeightConstraint.constant = 108
    }

    self.view.layoutIfNeeded()
}
2
On

As I've mentioned in the comment, you should have used UIStackView for your top / bottom views visibility controlling.

You will need a UIStackView with following attributes:

  • Axis: Vertical
  • Alignment: Fill
  • Distribution: Fill
  • Spacing: As per your need

The stack view will contain the Top View, Container View and Bottom View respectively as its sub views.

You need to pin all the sides (leading, trailing, top & bottom) of this stack view to its superview (view controller's view). And as you need some height constraints for your top & bottom view, you give them as your need.


So basically your stack view is now capable of resizing its sub views when you hide any of them. You just need to perform:

theSubViewNeedsTobeHidden.isHidden = true

I've made a working demo for you that can be found here.