Auto Layout constraints allow us to create views that dynamically adjust to different size classes and positions. The constraints will make sure that your views adjust to any size changes without having to manually update frames or positions.
Can you imagine a world without Auto Layout? We used to calculate frames ourselves or we used auto resizing masks (yes, that is related to the translatesAutoresizingMaskIntoConstraints
property). It’s only been since iOS 6 that we can depend on those dynamic adjusting constraints.
By using those extensions you prevent yourself from writing a lot of boilerplate code.
There’s a lot to cover but before I dive in I want to shortly discuss the difference between writing constraints in code vs using the Interface Builder.
Interface builder vs programmatically writing Auto Layout constraints
Writing Auto Layout constraints programmatically vs using the Interface Builder (IB), it’s always a hot topic. I decided to ask my followers with a poll on Twitter which resulted in 851 votes and a lot of replies with strong opinions.
Do you do auto layout in code or interface builder? #swiftlang #iosdev— Antoine v.d. SwiftLee ? (@twannl) October 20, 2019
Although there is a winner the results are very close and I’m quite surprised by the spread. It confirms the fact that it’s a hot topic as developers clearly don’t agree with one solution. You might think that we can be happy with the idea that SwiftUI will solve this after all.
Either way, we’re not in a world yet where we can completely rely on SwiftUI. It’s still often a decision to make whether you go for writing your layout in code or going for storyboards or XIBs.
Following personal experience and the comments on Twitter there’s a few pros and cons of writing your layout in code.
Pros and cons for writing Auto Layout constraints in code
Pros
- Easier to merge in GIT
- The code is easier to debug
- Constraints might be easier to overview
Cons
- There’s no visual representations. The IB allows you to see your view in different size classes.
- You might end up with a lot of layout code in your view controllers
Both solutions have quite a learning curve although it might be easier to write constraints in code if you’ve had experience with setting up constraints in the Interface Builder. Therefore, I would personally recommend to always start with Storyboards if you just get started with building apps.
Combining the Interface Builder with constraints in code could be a solution as well. However, keep in mind that this is less consistent and might make it even harder to understand what’s going on. Writing constraints while using the Interface Builder as well could be seen as writing Auto Layout side effects.
If you’re working in teams you might want to prevent yourself from solving a lot of merge conflicts by writing your constraints programmatically. In the end, it’s mostly personal taste. Just like the poll results show there’s no golden way.
Writing Auto Layout constraints in code
Once you decided to write your constraints in code you enter a world with a lot of possibilities. Luckily enough, these type of constraints are no longer needed:
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[icon(==postDate)]-20-[titleLabel(120@250)]-20@750-[postDate(>=50)]-|", metrics: nil, views: views)
It’s an example constraint written in the Auto Layout Visual Format Language. It’s still supported and it might be something you encounter when opening an old project but it’s not something I’ll cover in this blog post.
Instead, we’ll focus on using Layout Anchors as introduced in iOS 9. It’s a great API that, nowadays, allows you to write constraints in code quite easily.
Writing constraints by using Layout Anchors
First of all, we need to set the translatesAutoresizingMaskIntoConstraints
to false. This is to prevent the view’s auto-resizing mask to be translated into Auto Layout constraints and affecting your constraints.
After that, you start by creating an array of constraints. In this array, you define your set of constraints:
let constraints = [
view.centerXAnchor.constraint(equalTo: superview.centerXAnchor),
view.centerYAnchor.constraint(equalTo: superview.centerYAnchor),
view.widthAnchor.constraint(equalToConstant: 100),
view.heightAnchor.constraint(equalTo: view.widthAnchor)
]
NSLayoutConstraint.activate(constraints)
This example shows the basics of writing constraints and should be both readable and understandable. It creates a square and centers it in its superview. The last line is needed to actually activate the constraints so they make your layout appear as expected.
Each UIView
comes with a collection of anchor properties that allow you to set up relations between views:
extension UIView {
/* Constraint creation conveniences. See NSLayoutAnchor.h for details.
*/
open var leadingAnchor: NSLayoutXAxisAnchor { get }
open var trailingAnchor: NSLayoutXAxisAnchor { get }
open var leftAnchor: NSLayoutXAxisAnchor { get }
open var rightAnchor: NSLayoutXAxisAnchor { get }
open var topAnchor: NSLayoutYAxisAnchor { get }
open var bottomAnchor: NSLayoutYAxisAnchor { get }
open var widthAnchor: NSLayoutDimension { get }
open var heightAnchor: NSLayoutDimension { get }
open var centerXAnchor: NSLayoutXAxisAnchor { get }
open var centerYAnchor: NSLayoutYAxisAnchor { get }
open var firstBaselineAnchor: NSLayoutYAxisAnchor { get }
open var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
}
Each Anchor it returns subclasses from NSLayoutAnchor
which comes with a few common methods to set relationships. These include equal to, greater than, and less than or equal to relationships. It’s best to explore its documentation and get yourself familiar with the available APIs.
Order of constraints
Once you start writing your constraints its important to keep in mind the order of your constraints if you start working with constants.
Taking the following set of constraint:
let constraints = [
innerSquare.topAnchor.constraint(equalTo: outerSquare.topAnchor),
innerSquare.leftAnchor.constraint(equalTo: outerSquare.leftAnchor, constant: 40),
outerSquare.bottomAnchor.constraint(equalTo: innerSquare.bottomAnchor),
outerSquare.rightAnchor.constraint(equalTo: innerSquare.rightAnchor, constant: 40)
]
NSLayoutConstraint.activate(constraints)
Results in the following image in which the outer square is blue and the inner square is red:
If we would change the order of the outer square and inner square the constants need to be set with negative values:
let constraints = [
innerSquare.topAnchor.constraint(equalTo: outerSquare.topAnchor),
outerSquare.leftAnchor.constraint(equalTo: innerSquare.leftAnchor, constant: -40),
outerSquare.bottomAnchor.constraint(equalTo: innerSquare.bottomAnchor),
innerSquare.rightAnchor.constraint(equalTo: outerSquare.rightAnchor, constant: -40)
]
NSLayoutConstraint.activate(constraints)
The same counts for the top and bottom constraints. Therefore, keep in mind the following order when setting constraints and switch the view and superview for the last two anchors:
- Top
- Left
- Bottom
- Right
This basically comes down to counterclockwise.
Available layout guides
Each UIView
comes with a few layout guides which can also be used as anchors.
layoutMarginsGuide
: Set constraints and keep the layout margins as spacingreadableContentGuide
: Constraints the width to a size that is easy for the user to readsafeAreaLayoutGuide
: Represents the portion of your view that is unobscured by bars and other content
You could use the guides as following:
let constraints = [
outerSquare.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor),
outerSquare.leftAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.leftAnchor),
viewController.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: outerSquare.bottomAnchor),
viewController.view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: outerSquare.rightAnchor)
]
Supporting Right-to-Left languages
Although it might seem really obvious to just use leftAnchor
and rightAnchor
, you might want to think about using leadingAnchor
and trailingAnchor
instead. Doing so adds support for Right-to-Left languages to your views. This is mostly important for views like labels in which you want to make sure that they get flipped for Right-to-Left languages.
Performance of dynamically enabling and disabling constraints
Performance and Auto Layout constraints can be a topic on its own. Therefore, I’d like to refer you to this great talk of WWDC 2018: High Performance Auto Layout.
Valuable extensions to make your life easier
There’s a lot you can do to make it easier to work with constraints in code. By using those extensions you prevent yourself from writing a lot of boilerplate code.
Constraining a view to its superview
A common extension is to create a method to easily constraint view in its superview:
extension UIView {
/// Returns a collection of constraints to anchor the bounds of the current view to the given view.
///
/// - Parameter view: The view to anchor to.
/// - Returns: The layout constraints needed for this constraint.
func constraintsForAnchoringTo(boundsOf view: UIView) -> [NSLayoutConstraint] {
return [
topAnchor.constraint(equalTo: view.topAnchor),
leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.bottomAnchor.constraint(equalTo: bottomAnchor),
view.trailingAnchor.constraint(equalTo: trailingAnchor)
]
}
}
Handling priorities
Once you have to set priorities to your constraints to prevent breaking constraints you might be happy with the following extension:
extension NSLayoutConstraint {
/// Returns the constraint sender with the passed priority.
///
/// - Parameter priority: The priority to be set.
/// - Returns: The sended constraint adjusted with the new priority.
func usingPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
self.priority = priority
return self
}
}
extension UILayoutPriority {
/// Creates a priority which is almost required, but not 100%.
static var almostRequired: UILayoutPriority {
return UILayoutPriority(rawValue: 999)
}
/// Creates a priority which is not required at all.
static var notRequired: UILayoutPriority {
return UILayoutPriority(rawValue: 0)
}
}
An Auto Layout Property Wrapper
Prevent yourself from constantly writing:
translatesAutoresizingMaskIntoConstraints = false
By making use of the following Property Wrapper:
@propertyWrapper
public struct UsesAutoLayout<T: UIView> {
public var wrappedValue: T {
didSet {
wrappedValue.translatesAutoresizingMaskIntoConstraints = false
}
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
wrappedValue.translatesAutoresizingMaskIntoConstraints = false
}
}
final class MyViewController {
@UsesAutoLayout
var label = UILabel()
}
If you want to learn more about Property Wrappers, check out my blog post Property wrappers to remove boilerplate code in Swift.
Debugging Auto Layout constraints
[LayoutConstraints] Unable to simultaneously satisfy constraints.
This is probably a common message for you if you’ve been working with constraints for a while now. I wrote a blog post on User Interface debugging tools that can help you solve Auto Layout issues: UI Debugging by making use of third-party apps.
The need for an external dependency when writing constraints in code
There are a lot of external dependencies that say that they make it easier to write constraints. I hope that the above examples showed that it’s already quite easy to write constraints in code by making use of the default APIs. External frameworks introduce custom code patterns that might not be familiar to current and future colleagues.
Therefore, I would recommend you to make use of Layout Anchors and stick with Apple’s default APIs.
Conclusion
Writing your Auto Layout constraints in code should be a bit more easy for you after reading this post. It’s a great alternative to setting up constraints in the Interface Builder.
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!