Table of Contents
This DSL aims to make writing and reading constraints more intuitive by making intent clear and removing boilerplate code. Result builders are used to make constraint batching and activation easier and more efficient.
A single constraint can be defined like so:
view.bottom == 4 * button.top + 10
This is similar to the internal linear representation as discussed by Apple. From the Apple documentation:
Each constraint is a linear equation with the following format:
item1.attribute1 = multiplier × item2.attribute2 + constant
Tired of this?
NSLayoutConstraint.activate([
view.centerYAnchor.constraint(equalTo: view1.centerYAnchor),
view.centerXAnchor.constraint(equalTo: view1.centerXAnchor),
view1.heightAnchor.constraint(equalToConstant: 200),
view1.widthAnchor.constraint(equalToConstant: 200),
view2.topAnchor.constraint(equalTo: view1.topAnchor, constant: -10),
view2.bottomAnchor.constraint(equalTo: view1.bottomAnchor, constant: 10),
view2.leadingAnchor.constraint(equalTo: view1.leadingAnchor, constant: -10),
view2.trailingAnchor.constraint(equalTo: view1.trailingAnchor, constant: 10),
view3.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
view3.heightAnchor.constraint(equalTo: view1.heightAnchor, multiplier: 0.5),
view3.widthAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 0.5),
NSLayoutConstraint(item: view3, attribute: .centerX, relatedBy: .equal,
toItem: view1, attribute: .trailing, multiplier: 1/3, constant: 0),
view4.trailingAnchor.constraint(equalTo: view3.trailingAnchor, constant: 10),
view4.leadingAnchor.constraint(equalTo: view3.leadingAnchor, constant: -10),
view4.topAnchor.constraint(equalTo: view2.bottomAnchor, constant: 50),
view4.heightAnchor.constraint(equalToConstant: 100),
])
Constraints {
view.centerXY == view1.centerXY
view1.size == 200
view2.edges == view1.edges + UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
view3.bottom == view.safeAreaLayoutGuide.bottom
view3.size == view1.size / 2
view3.centerX == view.trailing / 3
view4.horizontalEdges == view3.horizontalEdges + UIEdgeInsets(left: 10, right: 10)
view4.top == view2.bottom + 50
view4.height == 100
}.activate()
Constraint Builder provides a wrapper over the LayoutAnchor API. Basic anchors consist off:
- top
- bottom
- leading
- trailing
- centerX
- centerY
- height
- width
- left
- right
- firstBaseline
- lastBaseline
Composite Anchors combine together basic anchors, enabling setting multiple constraints at once.
Composite | Basic Anchors |
---|---|
size | width, height |
centerXY | centerX, centerY |
horizontalEdges | leading, trailing |
verticalEdges | top, bottom |
edges | leading, trailing, top, bottom |
Relations are expressed using swift operators:
== // NSLayoutConstraint.Relation.equal
=> // NSLayoutConstraint.Relation.greaterThanOrEqual
<= // NSLayoutConstraint.Relation.lessThanOrEqual
button.leading == button2.trailing + 10
button.leading - 10 == button2.trailing
button.leading == 10 + button2.trailing
// these expressions result in equivalent constraints
The same mathematical rules apply to multipliers which allow us to express relative alignment:
button.leading == 2 * button2.trailing / 3
view.centerY == superview.centerY
view.centerX == superview.centerX
// or using the composite anchor - centerXY:
view.centerXY == superview.centerXY
view.top == superview.top
view.bottom == superview.bottom
view.leading == superview.leading
view.trailing == superview.trailing
// or using composite anchors - edges:
view.horizontalEdges == superview.horizontalEdges
view.verticalEdges == superview.verticalEdges
// in one go:
view.edges == superview.edges
Note: composite edge anchors use leading and trailing anchors under the hood because those are recommended as the default choice.
Composite Anchors also support setting insets.
When it comes to using composite anchors, setting insets is more useful compared to 'shifting'.
view1.edges == view2.edges + UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
view1.horizontalEdges == 10
view1.horizontalEdges == view2.horizontalEdges + UIEdgeInsets(left: 10, right: 10)
// this DSL provides a shortened init method
Constraint Builder automatically negates some of the constants when you provide a UIEdgeInsets object.
Constrain views to be the same size:
button.height == button2.height
button.width == button2.width
// or using composite anchor - size:
button.size == button2.size
button.size == button2.size * 2 // make button1 twice the size of button2
Set fixed size:
button.height == 100
button.width == 200
// or with composite anchor - size:
button.size == CGSize(width: 200, height: 100)
button.size == 100 // square
Constraints are not activated individually by default because that is not efficient and can slow down view rendering. Multiple constraints should be activated using NSLayoutConstraint.activate([])
according to Apple.
Therefore, we need an intuitive way to batch and activate constraints.
button.height == 100 // returns NSLayoutConstraint
button.size == 100 // returns [NSLayoutConstraint]
Constraint Builder uses Results Builders to provide a simple API to batch together single constraints with arrays of constraints returned my composite anchors.
Constraints {
button.height == 100 // returns NSLayoutConstraint
button.size == 100 // returns [NSLayoutConstraint]
} // Builds result implicitly and returns [NSLayoutConstraint]
You can activate an individual or an array of constraints using the .activate()
method.
This uses NSLayoutConstraint.activate([])
under the hood ensuring performance.
Constraints {
mapView.size == 200 ~ .required
mapView.centerXY == view.centerXY
view.centerX == view2.centerX
mapView.bottom == view2.top - 10
view2.size == mapView.size / 2
}.activate()
Result builders also allow us to include additional control flow within the closure.
Constraints {
if somethingIsTrue {
mapView.size == 200 ~ .required
mapView.centerXY == view.centerXY
} else {
view.centerX == view2.centerX
}
Constraints {
mapView.bottom == view2.top - 10
view2.size == mapView.size / 2
}.priority(.required)
}.activate()
Set priority on individual constraints using ~
operator
view1.top == view2.top + 20 ~ .required
view1.bottom == view2.bottom + 20 ~ .priority(500) // set custom priority
view1.trailing == view2.trailing + 20 ~ .almostRequired
Furthermore .priority()
method can be called on arrays and individual constraints.
Install using Swift Package Manager
Simeon Rumyannikov
- Linked-in
- Email: [email protected]
This project is shipped under the MIT license
- Swift by Sundell: Building DSLs in Swift
- SwiftAutoLayout Package
- Anchorage Package
- Cartography Package
I have built a fully functional and tested Auto Layout with some of Swifts newest features, documenting the process.