LayoutBuilder is an operator-based DSL layout relationship builder. It allows you to create constraints programmatically simpler and more elegant than ever. And it is very flexible.
LayoutBuilder is a set of structures, extensions, and operators that allows you to create NSLayoutConstraint using a linear equation. For example, if you want to create horizontal relation between a button and its superview, you need to write a simple line of code:
let constraint = button.layout(.leading) == 20
The result of this line will be NSLayoutConstraint. You can use Layout initializer with closure parameter that uses an internal ResultBuilder (ex FunctionBuilder) to activate constraints:
Layout {
button.layout(.leading) == 20
}
There are many ways to create constraints through a linear equation. We will consider them in the description below.
- Clean linear equation way to creating NSLayoutConstraint.
- An intuitive set of operators for using in the equation.
- Flexible combinable parameters.
- Simple constraints activation.
- Ability to use conditional expressions in the constraint activation closure.
- Convenient way to update Layout.
- iOS 9.0+ / Mac OS X 10.10+
- Xcode 12.5+
- Swift 5.4+
- If you need help, use Stack Overflow. (Tag 'layoutbuilder')
- If you'd like to ask a general question, use Stack Overflow.
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, submit a pull request.
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapods
To integrate LayoutBuilder into your Xcode project using CocoaPods, specify it in your Podfile
:
platform :ios, '9.0'
use_frameworks!
target '<Your Target Name>' do
pod 'LayoutBuilder'
end
Then, run the following command:
$ pod install
Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
Xcode 12.5+ is required to build LayoutBuilder using Swift Package Manager.
To integrate LayoutBuilder into your Xcode project using Swift Package Manager, add it to the dependencies value of your Package.swift
:
dependencies: [
.package(url: "https://github.com/multimediasuite/LayoutBuilder.git", .upToNextMajor(from: "0.5.1"))
]
Or add dependency manually in Xcode. File -> Swift Packages -> Add Package Dependency... then enter the package URL 'https://github.com/multimediasuite/LayoutBuilder.git' and click Next button.
If you prefer not to use either of the aforementioned dependency managers, you can integrate LayoutBuilder into your project manually.
In Apple's documentation, each constraint is a linear equation with the following format:
item1.attribute1 = multiplier × item2.attribute2 + constant
This is a convenient formula, and I put it at the heart of the framework with one more addition: priority operator !
. So in LayoutBuilder framework final core equation has the following format:
item1.attribute1 = multiplier × item2.attribute2 + constant ! priority
And there is a similar equation for creating an array of constraints (for attribute sets):
item.attributeSet = multiplier * item2 + constant ! priority
Note: It’s important to note that the equations shown above represent equality, not assignment.
There is possible not to use unnecessary parameters on the right side of the equation if your context implies it.
You must use an item with attribute at the left side of equation, and at the right side, you can use an item or item with attribute (except for using attribute set at the left side).
Item is UIView
or NSView
. If you want to create an item with attribute or attribute set, use layout(_ :)
method of item:
var itemWithAttribute = view.layout(.leading) //The result is LayoutItem object that contains view and attribute
Note: If you use an item without an attribute on the right side, the second attribute will be the same as the first attribute.
Layout
is a class that uses LayoutResultBuilder
to create and control constraints in its context. There are multiple properties and functions you can use to make your layout experience more flexible.
Property | Description |
---|---|
isActive |
Layout active status. Shows built constraints are active or not. Defines that all new constraints will be active or not. True by default |
constraints |
Contains all constraints in a Layout instance |
Function | Description |
---|---|
init(_ :) |
Layout initializer that has one parameter - LayoutResultBuilder closure. Creates a Layout instance and activates constraints from the build block. |
rebuild(_ :) |
Deactivates all constraints previously built in the Layout instance and replaces them with new constraints build. Activates them if isActive is true. |
append(_ :) |
Appends new constraints to exist in the Layout instance constraints. Activates them if isActive is true. |
activate() |
Activates all constraints in the Layout instance |
deactivate() |
Deactivates all constraints in the Layout instance |
Note: You can call Layout initializer without assigning its result to property. It activates all constraints you added in the building block, but you can't have direct access to constraints and Layout functions.
There are multiple operators for NSLayoutConstraint creation. They can be divided into two groups: creation and modification operators.
Creation operators are the relationship operators at the same time and expects firstItem
and firstAttribute
at the left side and secondItem
and secondAttribute
at the right side. Also, you can use modification operators on the right side. Still, you need use them in the direct sequence: multiplier *
-> constant +
-> priority !
. Finally, you not able to use the multiplier operator if you don't use item or item.attribute in the equation.
Operator | Description |
---|---|
== |
Equal operator. The result constraint requires the first attribute to be exactly equal to the modified second attribute. |
>= |
Greater than or equal relation operator. The result constraint requires the first attribute to be greater than or equal to the modified second attribute. |
<= |
Less than or equal operator. The result constraint requires the first attribute to be less than or equal to the modified second attribute. |
Operator | Description |
---|---|
+ |
Constant operator. Expects item or item with attribute at the left side and floating-point value at the right side. 0 by default. |
- |
Negative Constant operator. Expects item or item with attribute at the left side and floating-point value at the right side. 0 by default. |
* |
Multiplier operator. Expects floating-point value at the left side and item or item with attribute at the right side. 1 by default. |
! |
Priority operator. Expects item or item with attribute or constant at the left side and Priority value at the right side. .required by default. |
Note: You can use floating-point value at the right side as the first parameter without multiplier operator and item or item with attribute. This will mean that the second attribute will be the same as the first attribute, and the superview of the first item will set as the second item (except when the first attribute is
.width
or.height
).
Call Layout
initializer to activate constraints. Layout initializer's single parameter is a closure that uses Result Builder to accept multiple NSLayoutConstraints parameters. You can use conditional statements inside the closure. You can use for-in cycle inside the closure.
In addition to attributes, you can use a set of attributes in an equation. Just like attributes, attribute sets are used in the .layout(_ :)
method of view. But there is one difference. If you use a set of attributes instead of a single attribute on the left side of the equation, you cannot specify a view with attribute on the right side, only a view. But modification parameters are staying similar.
Set name | Attributes |
---|---|
edges |
.top , .left , .right , .bottom |
horizontal |
.left , .right |
vertical |
.top , .bottom |
size |
.width , .height |
center |
.centerX , .centerY |
Note: If you use attribute set in
layout(_ :)
method, attributes will accept constants from the equation as inset constants. It means that constants for.right
and.bottom
attributes will be reversed for keeping view "in the box" when you use attribute sets that contain.right
and.bottom
attributes.
import UIKit
import LayoutBuilder
final class ViewController: UIViewController {
lazy var redView = UIView()
lazy var blueView = UIView()
var isBlueViewSizeEqualToRedView = true
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(redView)
view.addSubview(blueView)
redView.backgroundColor = .red
blueView.backgroundColor = .blue
Layout {
redView.layout(.height) == 100
redView.layout(.width) == 0.5 * view
redView.layout(.centerX) == 0
redView.layout(.centerY) == view
if isBlueViewSizeEqualToRedView {
blueView.layout(.size) == redView
} else {
blueView.layout(.width) >= 0.2 * redView + 10
blueView.layout(.height) == redView.layout(.width)
}
blueView.layout(.bottom) == view.layout(.centerY) + 100
blueView.layout(.centerX) == 0
}
}
}
//RedView.height EQUAL to CONSTANT 40
redView.layout(.height) == 40
//RedView.leading EQUAL to BlueView.trailing with CONSTANT 8
redView.layout(.leading) == blueView.layout(.trailing) + 8
//RedView.width EQUAL to BlueView.width with MULTIPLIER 0.5 and CONSTANT 20
redView.layout(.width) == 0.5 * blueView + 20
//RedView.centerY EQUAL to RedView.Superview
redView.layout(.centerY) == 0
//BlueView.width GREATER THAN OR EQUAL to RedView.width with PRIORITY defaultLow
blueView.layout(.width) >= redView ! .defaultLow
//BlueView.centerX LESS THAN OR EQUAL to BlueView.Superview with CONSTANT 20 and PRIORITY defaultHigh
blueView.layout(.centerX) <= 20 ! .defaultHigh
//RedView.centerX EQUAL to BlueView.top with MULTIPLIER 0.8 and CONSTANT 16 and PRIORITY defaultLow
redView.layout(.centerX) == 0.8 * blueView.layout(.top) + 16 ! .defaultLow
//RedView.edges EQUAL tp RedView.Superview.edges with CONSTANT 20 and PRIORITY .defaultLow
redView.layout(.edges) == 20 ! .defaultLow
//BlueView.size EQUAL tp RedView.size with CONSTANT 20
blueView.layout(.size) == redView + 20
//etc...
//Create with constraints
let layout = Layout {
redView.layout(.left) == 0
redView.layout(.top) == 0
redView.layout(.width) == 100
redView.layout(.height) == 100
}
//Remove all constraints activated in the Layout instance before and add new constraints
layout.rebuild {
redView.layout(.left) == 100
redView.layout(.top) == 200
redView.layout(.width) == 300
redView.layout(.height) == 400
}
//Append new constraints
layout.append {
blueView.layout(.leading) == redView.layout(.trailing)
}
//Activate all constraints in the Layout instance
layout.activate()
//Deactivate all constraints in the Layout instance
layout.deactivate()
- Ihor Malovanyi (@multimediasuite)
LayoutBuilder is released under the MIT license. See LICENSE for details.