Search code examples
iosswiftconstraints

Swift - Problem with constraints priority


I currently have a layout with two large buttons, one on top of the other, and a number of icon button below them.

The second large button can appear or not. What I want is for the icon button's top constraint to be the visible large button's bottom constraint. I'm using priority to set up two constraints for the icon button:

    let topToBut2 = iconButton.topAnchor.constraint(equalTo: button2.bottomAnchor)
    topToBut2.isActive = true
    
    let topToBut1 = iconButton.topAnchor.constraint(equalTo: button1.bottomAnchor)
    topToBut1.priority = UILayoutPriority(500)
    topToBut1.isActive = true

On viewDidLoad, depending on the data available, I do

    button2.removeFromSuperview()

However, as soon as I remove button2 I get

'Unable to activate constraint with anchors <...> and <...> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal.'

What am I doing wrong?


Solution

  • You are getting an error because you are removing the button2:

    button2.removeFromSuperview()
    

    And thereafter you are trying to activate a constraints on it:

    let topToBut1 = iconButton.topAnchor.constraint(equalTo: button1.bottomAnchor)
    topToBut1.priority = UILayoutPriority(500)
    topToBut1.isActive = true
    

    So here you approach to remove the second button in order to the first button top constraints to be active is not really good. You can instead just check if the second button should be visible and setup the iconButton constraint accordingly, without using priority or two different constraints:

    if secondButtonVisible {
        iconButton.topAnchor.constraint(equalTo: button2.bottomAnchor).isActive = true
    } else {
        iconButton.topAnchor.constraint(equalTo: button1.bottomAnchor).isActive = true
    }
    

    But also you should not forget to set the second button hidden property to false(and not just remove it from the superview), because otherwise it will be visible under iconButton.

    If you are open to an other solution, as the other answer suggests here, you can use a UIStackView to do all the work for you. Just add the two buttons as a subView to the UIStackView as follows:

    stackView.addArrangedSubview(button1)
    stackView.addArrangedSubview(button2)
    

    And when you want to hide your second button you just change it's hidden property:

    button2.isHidden = true
    

    Which will hide the second button and update the layout accordingly. As per the documentation:

    The stack view automatically updates its layout whenever views are added, removed or inserted into the arrangedSubviews array, or whenever one of the arranged subviews’s isHidden property changes.