How to update swift Layout Anchors?

Justin picture Justin · Aug 24, 2016 · Viewed 56.8k times · Source

Trying to find a solution to update multiple constraints for multiple UI elements on an event. I have seen some examples of deactivating, making a change, then reactivating constraints, this method seems impractical for the 24 anchors I am working with.

One of my sets of changes:

ticketContainer.translatesAutoresizingMaskIntoConstraints = false
ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor).active = true
ticketContainer.leftAnchor.constraintEqualToAnchor(self.rightAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(200.0).active = true

ticketContainer.leftAnchor.constraintEqualToAnchor(self.leftAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(100.0).active = true

Answer

Daniel Hall picture Daniel Hall · Aug 24, 2016

Have you tried saving the relevant constraints you created using the layout anchors to properties, and then just changing the constant? E.g.

var ticketTop : NSLayoutConstraint?

func setup() {
    ticketTop = ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor, constant:100)
    ticketTop.active = true
}

func update() {
    ticketTop?.constant = 25
}

Possibly More Elegant

Depending on your taste for writing extensions, here is a possibly more elegant approach that doesn't use properties, but instead creates extension methods on NSLayoutAnchor and UIView to aid in more succinct usage.

First you would write an extension on NSLayoutAnchor like this:

extension NSLayoutAnchor {
    func constraintEqualToAnchor(anchor: NSLayoutAnchor!, constant:CGFloat, identifier:String) -> NSLayoutConstraint! {
        let constraint = self.constraintEqualToAnchor(anchor, constant:constant)
        constraint.identifier = identifier
        return constraint
    }
}

This extension allows you to set an identifier on the constraint in the same method call that creates it from an anchor. Note that Apple documentation implies that XAxis anchors (left, right, leading, etc.) won't let you create a constraint with YAxis anchors (top, bottom, etc.), but I don't observe this to actually be true. If you did want that type of compiler checking, you would need to write separate extensions for NSLayoutXAxisAnchor, NSLayoutYAxisAnchor, and NSLayoutDimension (for width and height constraints) that enforce the same-axis anchor type requirement.

Next you would write an extension on UIView to get a constraint by identifier:

extension UIView {
    func constraint(withIdentifier:String) -> NSLayoutConstraint? {
        return self.constraints.filter{ $0.identifier == withIdentifier }.first
    }
}

With these extensions in place, your code becomes:

func setup() {
    ticketContainer.topAnchor.constraintEqualToAnchor(anchor: self.topAnchor, constant:100, identifier:"ticketTop").active = true
}

func update() {
    self.constraint(withIdentifier:"ticketTop")?.constant = 25
}

Note that using constants or an enum instead of magic string names for the identifiers would be an improvement over the above, but I'm keeping this answer brief and focused.