Mortar allows you to create Auto Layout constraints using concise, simple code statements.
Use this:
Instead of:
Other examples:
Updating from a version prior to v1.1? Read this!
A change in the default Mortar constraint priority took effect in v1.1. Please read the README_DEFAULTS.md file for more information.
Why?
Yes, there are many Auto Layout DSLs to choose from. Mortar was created to fill perceived weaknesses in other offerings:
-
Mortar does not use blocks/closures like SnapKit or Cartography. These are distracting and ugly when coding constraints for controllers with many views.
-
Mortar is an attempt to get away from chaining methods like SnapKit does. Chained methods are good for providing semantic meaning to the line of code, but they are hard to parse quickly when you come back to your constraint section later.
-
Mortar supports multi-constraint macro properties (like
m_edges
,m_frame
,m_size
, etc). SwiftAutoLayout and other operator-based DSLs don't seem to have support for these in a concise form. Properties are prefixed with m_ to reduce potential for conflict with other View extensions. -
Mortar supports implicit property matching (other frameworks require declared properties on both sides of the statement.)
-
Mortar supports implicit tuple processing (you don't need to call out a tuple as a specific element like
CGRect
orCGSize
). -
Mortar supports multi-view alignment/constraints in a single line.
-
Mortar supports a robust compile-time VFL syntax that uses views directly (instead of dictionary lookups).
-
Additional goodies, like the
|+|
operator to visually construct view hierarchies rather than using tons of sequential calls toaddSubview()
.
Installing
You can install Mortar by adding it to your CocoaPods Podfile
:
If you would like to use the Mortar VFL language:
Or you can use a variety of ways to include the Mortar.framework
file from this project into your own.
Swift Version Support
This README reflects the updated syntax and constants used in the Swift 3 Mortar release.
For the Swift 2.x documentation, refer to theREADME_SWIFT2.md
file.
Disabling MortarCreatable
The default implementation of Mortar declares a MortarCreatable protocol (m_create), which in recent versions of
swift causes problems with classes that do not expose the default init() method.
Until the next major release, you can use:
To install a version of Mortar that does not attach this protocol to NSObject. You can then gain access to
m_create for whatever classes you want, with:
Usage
Mortar does not require closures of any kind. The Mortar operators (|=|
, |>|
and |<|
) instantiate and return constraints that are activated by default.
Mortar will set translatesAutoresizingMaskIntoConstraints
to false
for every view declared on the left side of an operator.
Equal, Less or Greater?
There are three mortar operators:
Attributes
Mortar supports all of the standard layout attributes:
m_left
m_right
m_top
m_bottom
m_leading
m_trailing
m_width
m_height
m_centerX
m_centerY
m_baseline
And iOS/tvOS spceific attributes:
m_firstBaseline
m_leftMargin
m_rightMargin
m_topMargin
m_bottomMargin
m_leadingMargin
m_trailingMargin
m_centerXWithinMargin
m_centerYWithinMargin
It also supports composite attributes:
m_sides -- (left, right)
m_caps -- (top, bottom)
m_size -- (width, height)
m_center -- (centerX, centerY)
m_cornerTL -- (top, left)
m_cornerTR -- (top, right)
m_cornerBL -- (bottom, left)
m_cornerBR -- (bottom, right)
m_edges -- (top, left, bottom, right)
m_frame -- (top, left, width, height)
Implicit Attributes
Mortar will do its best to infer implicit attributes!
The m_edges
attribute is implied when no attributes are declared on either side:
If an attribute is declared on one side, it is implied on the other:
You are required to put attributes on both sides if they are not the same:
Using Layout Guides
On iOS you can access the layout guides of a UIViewController
. An example from inside viewDidLoad()
that puts a view just below the top layout guide:
There is also a new UIViewController
property m_visibleRegion
to help align views to the region of controller's view that is below the top layout guide and above the bottom layout guide. To use this property you must have the MortarVFL extension installed.
Using m_visibleRegion
will create a "ghost" view as a subview of the controller's root view. This ghost view is hidden and non-interactive, and used only for positioning. Its class name is _MortarVFLGhostView
in case you see it inside the view debugger.
Multipliers and Constants
Auto Layout constraints can have multipliers and constants applied to them. This is done with normal arithmetic operators. Attributes must be explicitly declared on the right side When arthmetic operators are used.
You can also set attributes directly to constants:
Arithmetic can be done using tuples for multi-dimension attributes:
The special inset operator ~
operates on multi-dimension attributes:
Attributes in Tuples
You are allowed to put attributes inside of tuples:
Multiple Simultaneous Constraints
Multiple constraints can be created using arrays:
This might be a convenient way to align an array of views, for example:
If you put arrays on both sides of the constraint, it will only constrain elements at the same index. That is:
You can use this to create complex constraints on one line. For example, to create a 200-point high view that sits at the bottom of a container view:
Priority
You can assign priority to constraints using the !
operator. Valid priorities are:
.low
,.medium
,.high
,.required
- Any
UILayoutPriority
value
You can also put priorities inside tuples or arrays:
Default Priority
Defaults have changed in Mortar v1.1; See README_DEFAULTS.md if you are updating.
By default, constraints are given priority of .required
which is equal to 1000 (out of 1000) and is the same default used by Apple's constraint methods. Sometimes you
may want large batches of constraints to have a different priority, and it is messy to include something like
! .medium
after every constraint.
You can change the global base default value by using set
:
You can use this in the AppDelegate
to change the app-wide default constraint priority.
Because this can only be changed on the main thread, it is safe to call just before your
layout code. Keep in mind it will affect all future Mortar contraints! If you are adjusting
the default for a single layout section, it is usually wiser to use the stack mechanism
to change the default priority used in a frame of code:
You may only call the push/pop methods on the main thread, and Mortar will raise an exception if you do not
properly balance your pushes and pops.
Change Priority
You can change the priority of a MortarConstraint
or MortarGroup
by calling the changePriority
method. This takes either a MortarLayoutPriority
enum, or a UILayoutPriority
value:
Remember that you can't switch to or from Required
from any other priority level (this is an Auto Layout limitation.)
Create Deactivated Constraints
You can use the ~~
operator as a shorthand for constraint activation and deactivation. This makes the most sense as part of constraint declarations when you want to create initially-deactivated constraints:
Keeping Constraint References
The basic building block is the MortarConstraint
, which wraps several NSLayoutConstraint
instances that are relevant to multi-affinity attributes like m_frame
(4) or m_size
(2).
You can capture a MortarConstraint
for later reference:
The raw NSLayoutConstraint
elements can be accessed through the layoutConstraints
accessor:
You can create an entire group of constraints:
Mortar includes a convenient typealias to refer to arrays of MortarConstraint
objects:
You can now activate/deactivate constraints:
Replacing Constraints and Groups of Constraints
Constraints and groups have a replace
method that deactives the target and activates the parameter:
Compression Resistance and Content Hugging
Mortar provides some shorthand properties to adjust a view's compression resistance and content hugging priorities:
You can get the horizontal and vertical values independently, but not together:
MortarVFL
Mortar supports a VFL language that is roughly equivalent to Apple's own Auto Layout VFL langauge. The primary advanges are:
- You can use it directly with existing Mortar attribute support
- Views are referenced directly (instead of using dictionaries) for compile-time checking
- Full weight-based support for relative sizing
- More concise: Operator-based instead of function/string-based
MortarVFL is contrained to its own extension because it makes heavy use of custom operators. These operators may not be compatible with other libraries you are using, so we don't want Mortar core to conflict with those.
MortarVFL Internal Composition
The heart of a MortarVFL statement is a list of VFL nodes that are positioned sequentially along either the horizontal or vertical axis. A node list might look like:
VFL nodes:
- Represent either whitespace, one view, or multiple views
- Have either fixed spacing or weighted spacing
Nodes are separated by either a |
or ||
operator.
The |
operator introduces zero extra distance between nodes. You can use this operator to connect nodes directly with zero spacing, or insert your own fixed/weighted numerical value between them (e.g. | 30 |
or | ~~2 |
). In these cases, the 30
and ~~2
are considered nodes that represent whitespace (no attached view).
The ||
operator separates nodes by the default padding (8 points).
Nodes that represent views respect their intrinsic content as much as possible given the realvent constraints and priorities. View nodes can also constain a subscript that gives them a size constraint. You can use [==#]
to give the view a fixed size, or [~~#]
to give the view a weighted size. You can also reference other views, e.g. [==viewA]
to give the node's view the same constraint as the one it references.
MortarVFL will throw an error if you have cyclic view references, e.g. viewA[==viewB] | viewB[==viewA]
Arrays in a Node
As an advanced technique, you can use an array of views in a node. It would look something like this:
This positions the arrayed nodes in parallel with each other. In the above example, all three of viewB, viewC and viewD will be sized 40 points and be adjacent to viewA and viewE. This is very useful for complex grid-based layouts.
Capture
MortarVFL statements must be captured on at least one end by a view attribute. These captures look something like:
MortarVFL support horizontal and vertical spacing in a similar manner. The horizontal operators use the >
character while the vertical operators use the ^
character. Otherwise they act similarly. For example, the vertical version of the above statement would be:
Mortar will make sure your operators are compatible with the attributes you've selected. For example, using |>
with m_top
would be an axis mismatch and raise an exception.
Implicit Capture Attributes
If you don't provide attributes on the capture terminals, Mortar will derive them based on the axis and position:
Important Observation: Implicit attributes might be opposite of what you expect. This is because implicit attributes are normally used to capture views inside the bounds of parent views and so we use the outer edges, not the inner edges.
Implicit Surround
If you want the MortarVFL nodes to be inside the bounds of a single view, you can use the surround operators instead of placing the same view at both terminals.
The surround operators use either >>
or ^^
:
Single-Ended Statements
Up until now, all of the examples have shown statements bordered by two attributes (left and right, top and bottom).
For statements surrounded on both sides, you cannot have all fixed spacing. This means you will need at least one weighted or intrinsically sized node. This allows Mortar to make your constraints flexible between the terminals. You may see odd behavior if you only have intrinsically sized nodes, and their compression resistance and content hugging are artificially forced to .required.
For statements that have a single terminal, the opposite is true. You cannot use any weight-based nodes, and they must all be fixed size or intrinsic content size. This is because there is no second endpoing to use as an anchor for relative sizing.
Single-terminal statements look the same as the others, but trailing operators use a bang: !
Unfortunately this looks very much like the pipe operator, so don't be confused. Specifically, when just attaching a statement to a trailing attribute, use <!
, <!!
, ^!
or ^!!
.
Again, note the use of the !
bang symbol for trailing single-ended statements, and that there are no weight-based nodes. Leading single-ended statements use the operator with the pipe: |>
Examples
There are several examples of MortarVFL in the Examples/MortarVFL project.
Visual View Hierarchy Creation
Mortar provides the |+|
and |^|
operators to quickly add a subview or array of subviews. This can be used to create visual expressions of the view hierarchy.
Now this:
Turns into:
Alternatively, if you want to see the upper subviews at the beginning of the array (so that visually, the views closer to the top of the file are closer to the user), use the |^|
operator:
Initializing NSObject at Creation
Mortar extends NSObject
with the m_create
class function. This class function performs a parameter-less
instantiation of the class, and passes the new instance into the provided closure. This allows you to configure
an instance at creation-time, which is really nice for compartmentalizing view configuration.
As you can see in the below example, configuration of the view is separated from the code needed to attach it
to the view controller hierarchy and layout.