ww1989cn ww1989cn - 3 months ago 22
iOS Question

How to swap two buttons' constraints programmatically?

There are two buttons named

buttonA
and
buttonB
in a same view, I have set their constraints in storyboard. How can I exchange their constraints programmatically?

Answer

I was looking for an answer of this and I ended up doing it in the following way (all code samples are Swift 2.0).

Let's say you have a UIView called containerView which contains two UIViews: subviewA and subviewB.

The constraints for the subviews are actually defined within containerView (as per the rule of the "closest common ancestor").

This solution also assumes that the constraints have as a 'First Item' subViewA or subViewB and as a 'Second Item' containerView. If your setup is different you might need to modify the code accordingly.

So we have a function that swaps constraints:

private func swapFirstItemsInContraintsDefinedInThisView(superview: UIView, betweenItem item1: AnyObject, andItem item2: AnyObject)
{
    var constraintsToRemove: Array<NSLayoutConstraint>  = Array<NSLayoutConstraint>()
    var constraintsNew_item1: Array<NSLayoutConstraint>  = Array<NSLayoutConstraint>()
    var constraintsNew_item2: Array<NSLayoutConstraint>  = Array<NSLayoutConstraint>()

    for constraint in superview.constraints
    {
        if (constraint.firstItem === item1)
        {
            constraintsToRemove.append(constraint)
            constraintsNew_item1.append(NSLayoutConstraint(item: item2, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
        }
        else if (constraint.firstItem === item2)
        {
            constraintsToRemove.append(constraint)
            constraintsNew_item2.append(NSLayoutConstraint(item: item1, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
        }
    }

    superview.removeConstraints(constraintsToRemove);
    superview.addConstraints(constraintsNew_item1);
    superview.addConstraints(constraintsNew_item2);
}

So then you can call the following (in your ViewController somewhere):

self.swapFirstItemsInContraintsDefinedInThisView(self.containerView, betweenItem: subviewA, andItem: subviewB)
self.containerView.layoutIfNeeded()

Note that the effects of the swap won't be shown unless you call the layoutIfNeeded(). Depending on your needs, you might want to add this call as the last line of the body of the swap function.

By the way if you call layoutIfNeeded() in an UIAnimation context (in the animations closure/block) it would animate the transition.

Note that there a lot of potential improvements of this code, it's just an example of the basic approach. For instance we could have an extension on NSLayoutConstraint to allow initting it by copying another NSLayoutConstraint and changing only the 'First item' property (so that we avoid the long repeated lines of code above). Also the swap function itself could be defined in an extension of UIView. And performance-wise there are potential issues with the above implementation.