Easier Swift Layout Priorities

Working with the priorities of Auto Layout constraints can be a bit of a pain with Swift. You often just want to set a priority that is one more or one less than some other priority. Unfortunately the strong type safety of Swift means you cannot just treat the priority as a number the way you could with Objective-C.

Note: This no longer seems to be a problem. You can now directly increment and decrement UILayoutPriority values.

Layout Priorities

Consider a typical auto layout setup where I have a label and a text field arranged to horizontally fill a view:

label and text field

I want the label to stay at its natural size and the text field to stretch to fill the view width. The layout engine decides which field to stretch beyond its natural size by looking for the view with the lowest content hugging priority.

By default when you create a UILabel or a UITextField in code they have a horizontal content hugging priority of 250 (.defaultLow). To stop this layout from being ambiguous I need to increase the priority for the label so the layout engine stretches the text field. I want to be able to write this:

let labelPriority = UILayoutPriority.defaultLow + 1

Unfortunately the compiler complains that it cannot convert UILayoutPriority to an Int. Layout priorities have a raw value representation that is a float but UILayoutPriority is not itself a float and you cannot do addition or subtraction on it.

Note: This situation is common enough that if you create a label with Interface Builder it sets the content hugging priority to 251 for you.

Creating New Priorities

Maybe I am missing a trick but the best I can do is to first get the raw value of a priority:

let rawPriority = UILayoutPriority.defaultLow.rawValue

Then add one to the raw value before converting back to a UILayoutPriority:

let labelPriority = UILayoutPriority(rawPriority + 1)

Then finally I can set the priority of the label:

label.setContentHuggingPriority(labelPriority, for: .horizontal)

You can condense that down to a single line but it is not great.

Operator Overload

To be honest I am not a big fan of operator overloading. Most of the time I find it too clever for its own good. This time though I think it might be the best solution. It is easy enough to extend UILayoutPriority to support the + and - operators:

extension UILayoutPriority {
  static func +(lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority {
    return UILayoutPriority(lhs.rawValue + rhs)
  }

  static func -(lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority {
    return UILayoutPriority(lhs.rawValue - rhs)
  }
}

Now we can write this:

let labelPriority = UILayoutPriority.defaultLow + 1

I think the meaning is clear and gives a concise way to set a new priority relative to the default priorities:

label.setContentHuggingPriority(.defaultLow + 1, for: .horizontal)
view.setContentCompressionResistancePriority(.defaultHigh - 1, for: .vertical)