A compact but full-featured Auto Layout DSL for Swift

Related tags

Layout Mortar
Overview

Mortar

Swift 5.0 Swift 4.2 Swift 4.0

Mortar allows you to create Auto Layout constraints using concise, simple code statements.

Use this:

view1.m_right |=| view2.m_left - 12.0

Instead of:

addConstraint(NSLayoutConstraint(
    item:        view1,
    attribute:  .right,
    relatedBy:  .equal,
    toItem:      view2,
    attribute:  .left,
    multiplier:  1.0,
    constant:   -12.0
))

Other examples:

/* Set the size of three views at once */
[view1, view2, view3].m_size |=| (100, 200)

/* Pin a 200px high, same-width view at the bottom of a container */
[view.m_sides, view.m_bottom, view.m_height] |=| [container, container, 200]

/* VFL syntax */
view1 |>> viewA || viewB[==44] | 20 | viewC[~~2]

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 or CGSize).

  • 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 to addSubview().

Installing

You can install Mortar by adding it to your CocoaPods Podfile:

pod 'Mortar'

If you would like to use the Mortar VFL language:

pod 'Mortar/MortarVFL'

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 the README_SWIFT2.md file.

pod 'Mortar', '~> 1.6'  # Swift 5.0
pod 'Mortar', '~> 1.5'  # Swift 4.2
pod 'Mortar', '~> 1.4'  # Swift 4.0
pod 'Mortar', '~> 1.3'  # Swift 3.1

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:

pod 'Mortar/Core_NoCreatable'
pod 'Mortar/MortarVFL_NoCreatable'

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:

extension your_class_name: MortarCreatable { }

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:

view1.m_width |=| 40                // Equal
view1.m_width |>| view2.m_width     // Greater than or equal
view1.m_size  |<| (100, 100)        // Less than or equal

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:

view1.m_edges |=| view2.m_edges     // These two lines
view1         |=| view2             // are equivalent.

If an attribute is declared on one side, it is implied on the other:

view1.m_top   |>| view2             // These two lines
view1         |>| view2.m_top       // are equivalent.

You are required to put attributes on both sides if they are not the same:

view1.m_top   |<| view2.m_bottom

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:

// Super useful when trying to position views inside a navigation/tab controller!
view1.m_top   |<| self.m_topLayoutGuideBottom

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.

// Center a view inside the visible region of a UIViewController that is a child of
// a navigation controller or tab controller
textField.m_center |=| self.m_visibleRegion

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.

view1.m_size  |=| view2.m_size  * 2         // Multiplier   
view1.m_left  |>| view2.m_right + 20        // Constant
view1         |<| view2.m_top   * 1.4 + 20  // Both -- m_top is implied on the left

You can also set attributes directly to constants:

view1.m_width |=| 100                // Single-dimension constants
view1.m_size  |=| 150.0              // Set multiple dimensions to the same constant
view1.m_size  |>| (200, 50)          // Set multiple dimensions to a tuple value
view1.m_frame |<| (0, 0, 50, 100)    // Four-dimension tuples supported

Arithmetic can be done using tuples for multi-dimension attributes:

view1.m_size  |=| view2.m_size + (50, 30)
view1.m_size  |>| view2.m_size * (2, 3) + (10, 10)

The special inset operator ~ operates on multi-dimension attributes:

view1         |=| view2.m_edges ~ (20, 20, 20, 20)  // view1 is inset by 20 points on each side

Attributes in Tuples

You are allowed to put attributes inside of tuples:

view1.m_size  |=| (view2.m_width, 100)

Multiple Simultaneous Constraints

Multiple constraints can be created using arrays:

view1 |=| [view2.m_top, view3.m_bottom, view4.m_size]

/* Is equivalent to: */
view1 |=| view2.m_top
view1 |=| view3.m_bottom
view1 |=| view4.m_size
[view1, view2, view3].m_size |=| (100, 200)

/* Is equivalent to: */
[view1.m_size, view2.m_size, view3.m_size] |=| (100, 200)

/* Is equivalent to: */
view1.m_size |=| (100, 200)
view2.m_size |=| (100, 200)
view3.m_size |=| (100, 200)

This might be a convenient way to align an array of views, for example:

[view1, view2, view3].m_centerY |=| otherView
[view1, view2, view3].m_height  |=| otherView

If you put arrays on both sides of the constraint, it will only constrain elements at the same index. That is:

[view1.m_left, view2, view3] |=| [view4.m_right, view5, view6]

/* Is equivalent to: */
view1.m_left |=| view4.m_right
view2        |=| view5
view3        |=| view6

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:

[view.m_sides, view.m_bottom, view.m_height] |=| [container, container, 200]

Priority

You can assign priority to constraints using the ! operator. Valid priorities are:

  • .low, .medium, .high, .required
  • Any UILayoutPriority value
v0 |=| self.container.m_height
v1 |=| self.container.m_height ! .low
v2 |=| self.container.m_height ! .medium
v3 |=| self.container.m_height ! .high
v4 |=| self.container.m_height ! .required
v5 |=| self.container.m_height ! 300

You can also put priorities inside tuples or arrays:

view1        |=| [view2.m_caps   ! .high, view2.m_sides      ! .low]  // Inside array
view1.m_size |=| (view2.m_height ! .high, view2.m_width + 20 ! .low)  // Inside tuple

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:

MortarDefault.priority.set(base: .medium)

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:

MortarDefault.priority.push(.low)

v1 |=| v2 // Given priority .low automatically
...

MortarDefault.priority.pop()

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:

let c = view1 |=| view2 ! .low     // Creates 4 low-priority constraints (1 per edge)
c.changePriority(to: .high)        // Sets all 4 constraints to high priority

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:

let constraint = view1 |=| view2 ~~ .deactivated

// Later on, it makes more semantic sense to call .activate():
constraint.activate()

// Even though this is functionally equivalent:
constraint ~~ .activated

// It works with groups too:
let group = [
    view1 |=| view2
    view3 |=| view4
] ~~ .deactivated

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:

let constraint = view1.m_top |=| view2.m_bottom

The raw NSLayoutConstraint elements can be accessed through the layoutConstraints accessor:

let mortarConstraint = view1.m_top |=| view2.m_bottom
for rawLayoutConstraint in mortarConstraint.layoutConstraints {
    ...
}

You can create an entire group of constraints:

let group = [
    view1.m_origin |=| view2,
    view1.m_size   |=| (100, 100)
]

Mortar includes a convenient typealias to refer to arrays of MortarConstraint objects:

public typealias MortarGroup = [MortarConstraint]

You can now activate/deactivate constraints:

let constraint = view1.m_top |=| view2.m_bottom

constraint.activate()
constraint.deactivate()

let group = [
    view1.m_origin |=| view2,
    view1.m_size   |=| (100, 100)
]

group.activate()
group.deactivate()

Replacing Constraints and Groups of Constraints

Constraints and groups have a replace method that deactives the target and activates the parameter:

let constraint1 = view1.m_sides |=| view2
let constraint2 = view1.m_width |=| view2 ~~ .deactivated

constraint1.replace(with: constraint2)

let group1 = [
    view1.m_sides |<| view2,
    view1.m_caps  |>| view2,
]
        
let group2 = [
    view1.m_width |=| view2
] ~~ .deactivated

group1.replace(with: group2)

Compression Resistance and Content Hugging

Mortar provides some shorthand properties to adjust a view's compression resistance and content hugging priorities:

// Set both horizontal and vertical compression resistance priority simultaneously:
view1.m_compResist = 1

// Set horizontal and vertical compression resistance independently:
view1.m_compResistH = 300
view1.m_compResistV = 800

// Set both horizontal and vertical content hugging priority simultaneously:
view1.m_hugging = 1

// Set horizontal and vertical content hugging independently:
view1.m_huggingH = 300
view1.m_huggingV = 800

You can get the horizontal and vertical values independently, but not together:

// These getters are fine:
let c1 = view1.m_compResistH
let c2 = view1.m_compResistV
let h1 = view1.m_huggingH
let h2 = view1.m_huggingV

// These getters raise exceptions:
let cr = view1.m_compResist
let hg = view1.m_hugging

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.

pod 'Mortar/MortarVFL'

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:

viewA | viewB[==viewA] || viewC[==40] | 30 | viewD[~~1] | ~~1 | viewE[~~2]

// viewA has a size determinde by its intrinsic content size
// viewA is separated from viewB by 0 points (| operator)
// viewB has a size equal to viewA
// viewB is separated from viewC by the default padding (8 points; || operator)
// viewC has a fixed size of 40
// viewC is separated from viewD by a space of 30 points
// viewD has a weighted size of 1
// viewD is separated fom viewE by a weighted space of 1
// viewE has a weighted size of 2

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:

viewA || [viewB, viewC, viewD][==40] || viewE

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:

// viewB and viewC will take equal width between the 
// right edge of viewA and the left edge of viewD
viewA.m_right |> viewB[~~1] | viewC[~~1] <| viewD.m_left

// viewB and viewC will be equal width between the
// left/right edges of viewA, inset by 8pt padding
// and separated by 40pts.
viewA ||>> viewB[~~1] | 40 | viewC[~~1]

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:

// viewB and viewC will take equal height between the 
// bottom edge of viewA and the top edge of viewD
viewA.m_bottom |^ viewB[~~1] | viewC[~~1] ^| viewD.m_top

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:

// These are equivalent:
viewA.m_left |> viewB | viewC <| viewD.m_right
viewA        |> viewB | viewC <| viewD

// These are equivalent:
viewA.m_top  |^ viewB | viewC ^| viewD.m_bottom
viewA        |^ viewB | viewC ^| viewD

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 ^^:

// viewB and viewc will be equal width between the
// left/right edges of viewA, inset by 8pt padding
// and separated by 40pts.
viewA ||>> viewB[~~1] | 40 | viewC[~~1]

// viewB will be twice as tall as viewC; both will be between
// the top/bottom edges of viewA.
viewA |^^ viewB[~~2] | viewC[~~1]

// Using m_visibleRegion is helpful for layouts in child view controllers
// to get views laid out inside the visible region, not under nav/tab bars
self.m_visibleRegion ||^^ viewA | viewB | viewC

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 ^!!.

// viewB will be placed at the right edge of viewA and be 44pts wide.
// viewC will be placed 8pts (padding) right of viewB and will be 88pts wide.
viewA.m_right |> viewB[==44] || viewC[==88]

// viewC will be placed at the left edge of viewA and be 88pts wide.
// viewB will be placed 8pts (padding) left of viewC and will be 44pts wide.
viewB[==44] || viewC[==88] <! viewA.m_left

// viewB will be placed at the bottom edge of viewA and be 44pts high.
// viewC will be placed 8pts below viewB and respect its intrinsic content height.
viewA.m_bottom |^ viewB[==44] || viewC

// viewC will be placed at the top edge of viewA and be 88pts high.
// viewB will be placed 8pts above viewC and will be 44pts high.
viewB[==44] || viewC[==88] ^! viewA.m_top

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:

self.view.addSubview(backgroundView)
self.view.addSubview(myCoolPanel)
myCoolPanel.addSubview(nameLabel)
myCoolPanel.addSubview(nameField)

Turns into:

    self.view |+| [
        backgroundView,
        myCoolPanel |+| [
            nameLabel,
            nameField
        ]
    ]

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:

    self.view |^| [
        myCoolPanel |^| [      // myCoolPanel is added second and
            nameField,         // is therefore on top of backgroundView
            nameLabel
        ],
        backgroundView        
    ]

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.

class MyController: UIViewController {

    // Instantiation/configuration
    let myLabel = UILabel.m_create {
        $0.text          = "Some Text"
        $0.textAlignment = .center
        $0.textColor     = .red
    }

    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Hierarchy
        self.view |+| [
            myLabel
        ]

        // Layout
        myLabel.m_top     |=| self.view
        myLabel.m_centerX |=| self.view
    }
}
Comments
  • Ambiguous use of init

    Ambiguous use of init

    Hi @jmfieldman,

    As soon as I add import Mortar to a file, I start to notice errors occurring in files related to my project, but not related to Mortar. For example Ambiguous use of 'init()'. Have you heard of anything like this before? As soon as I remove the import Mortar, everything goes back to normal and I can successfully build my project.

    I'm using XCode Version 12.3 (12C33), Mortar 1.7.0, and building for iOS 12.

    opened by AdamBCo 15
  • Why Mortar needs to add the

    Why Mortar needs to add the "Ghost View" when I use VFL to define layout?

    @jmfieldman I am just curious, why we need to add the "Ghost View" to the view hierarchy when defining its layout with VFL? Why we need "Ghost View" at all? Is it some kind of simplification or workaround for a complex problem you couldn't solve otherwise? I tried to look into source code, but couldn't figure out why exactly we need it, why it's not possible to do the job without implicitly adding anything to given view hierarchy? I'd appreciate if you share some insights with me. Thanks!

    P.S. Thanks a lot for the library, it's amazing! I'm switching on Mortar from SnapKit and super happy with it.

    opened by maximkhatskevich 9
  • Mortar fails to compile using Carthage with XCode 8.3 / Swift 3.1

    Mortar fails to compile using Carthage with XCode 8.3 / Swift 3.1

    Carthage error:

    *** Building scheme "Mortar iOS" in Mortar.xcworkspace Build Failed Task failed with exit code 65: /usr/bin/xcrun xcodebuild -workspace /Users/.../Carthage/Checkouts/Mortar/Mortar.xcworkspace -scheme "Mortar iOS" -configuration Release -derivedDataPath /Users/.../Library/Caches/org.carthage.CarthageKit/DerivedData/Mortar/1.1.0 -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES clean build

    This usually indicates that project itself failed to compile.

    opened by urosk 7
  • Add weighted linear layouts

    Add weighted linear layouts

    This is a cleaned up, extended, and more mortar style version of an in house utility I wrote a while ago for a hackathon. We get a lot of of UI specs that require weighted spacing between the visible UI elements, which is more than UIStackView can do and is tedious to do with individual constraints. This PS makes constraining a bunch of views together in a row with weighted spaces between them trivial.

    opened by BrianKenny 7
  • Change default priority to match Apple?

    Change default priority to match Apple?

    I'm curious what the thinking is on mortar constraints defaulting to a lower priority(500) than constraints created with Apple's methods(required), and if you'd consider changing that? I had to add a bunch of ! .Requrieds until I found MortarDefault.Priority.setBase(.Required). Just recently I recommended Mortar to a team of devs for a different app, and within a day or two got asked why mortar constraints don't work constraining image view sizes. They do of course, but by default their priority is too low(500) to override the image's default compression resistance(750). I would imagine many/most new users will be surprised by this, and some will probably just assume it's a bug & bail.

    question 
    opened by BrianKenny 5
  • VFL supports intrinsic content size

    VFL supports intrinsic content size

    First off, great work on the VFL system. It's far more elegant and concise than the spacer view stuff I pushed a few weeks ago. I've finally found some free time to try it out, and there's one thing it still needed to be usefully for a lot of the situations where we were using spacer views. Specifically, support for intrinsic content size. Most labels, lots of images, etc. need that. Turns out it was only about dozen lines to add support for it. With this change views default to intrinsic content size(which is also the default in Apple's VFL) and you have to specify weights. In practice, having used a system like this for a bit over a year, I've found most visible views tend to be either intrinsic or constant size, and most weights are used in padding.

    opened by BrianKenny 4
  • Backport some NSLayoutConstraint functionality so Mortar can run on iOS7

    Backport some NSLayoutConstraint functionality so Mortar can run on iOS7

    Hi Jason. Love you take on autolayout :) I'm saddled w/ ios7 support for the foreseeable future. Fortunately, making Mortar support iOS 7 wasn't terribly difficult. In short, this PS replaces your use of NSLayoutConstraint's .active .activateConstraints and .deactivateConstraints w/ internal versions. They just call through on >= 8.0, and on <8.0 they find the correct common superview & use UIView .addConstraint or .removeConstraint instead.

    enhancement 
    opened by BrianKenny 4
  • Swift 4.2 and 5.0 support

    Swift 4.2 and 5.0 support

    This makes Mortar compatible to Swift 4.2 and 5.0.

    • test on Travis that it compiles with Swift 4.2 and 5.0
    • fix all Xcode 10.2 warnings
    • Podspec:
      • use swift_versions to define both supported Swift versions (introduced in Cocoapods 1.7.0)
      • upgrade swift_version to 5.0 (backwards compatibility for Cocoapods < 1.7.0)

    Tested integrations:

    • Xcode 10.0, Swift 4.2, Cocoapods 1.5.3 Podfile post_install: config.build_settings['SWIFT_VERSION'] = '4.0'
    • Xcode 10.0, Swift 4.2, Cocoapods 1.7.0, Podfile: supports_swift_versions '4.2', no post_install
    • Xcode 10.2, Swift 4.2, Cocoapods 1.7.0, Podfile: supports_swift_versions '>= 4.2', '< 5.0', no post_install
    • Xcode 10.2, Swift 5.0, Cocoapods 1.7.0, Podfile: supports_swift_versions '>= 4.2', '<= 5.0', no post_install
    opened by HeEAaD 2
  • Crash when trying to layout UIVisualEffectView

    Crash when trying to layout UIVisualEffectView

    The reason is that Mortar tries to add "Ghost View" on it, but it's not allowed - the "contentView" subviews supposed to be used for all subviews.

    opened by maximkhatskevich 2
  • Add the _NoCreatable subspecs

    Add the _NoCreatable subspecs

    Adding Core_NoCreatable and MortarVFL_NoCreatable subspecs for those that do not want NSObject to extend MortarCreatable by default

    Ref: https://github.com/jmfieldman/Mortar/issues/23

    opened by jmfieldman 2
  • m_create causes compilation issues in Xcode 9.3

    m_create causes compilation issues in Xcode 9.3

    In testing updating to Xcode 9.3, we discovered a subtle bug that is caused by applying the MortarNSObjectCreatable protocol to all NSObjects.

    This protocol is helpful in creating and configuring objects, but creates a problem in that it makes the init() method of NSObject public, even in subclasses where it wasn't public previously. We've run into common cases where a basic object can no longer be initialized because the compiler can't figure out if the subclass's init() or NSObject's init() method should be used. A simple example of this is trying to initialize NSCache.

    For this scenario, we've found that a better option is to add a method that happens after default initialization: http://khanlou.com/2016/06/configuration-in-swift/ This prevents any issues from occurring by making some methods visible that shouldn't be.

    In the short term, the MortarNSObjectCreatable should probably only be applied to classes that Mortar owns by default, which would allow others to add the method to classes on an as-need basis, rather than break things in unexpected ways.

    opened by JoeSzymanski 2
  • Infer superview & return (constraints, views)

    Infer superview & return (constraints, views)

    This PS does 2 things. It lets regular view nodes opt into the inferred superview logic that's currently used for ghost views, in the case where they don't have a superview when constraints are created. This often gets rid of the need to ad subviews explicitly. Second, all the capture list operators go from returning just the constraints to returning a tuple of the constraints and the views. I've been converting all my spacerview code to mortarVFL, and almost every case we have something like

    let theViews = [v1, v2, .........]
    self.view |+| theViews
    self.view |^^ v1 | v2 | .......
    theViews.m_...
    
    //Which now becomes something like
    
    let theViews = (self.view |^^ v1 | v2 ........).views
    theViews.m_....
    

    It only saves 2 lines, but one of those is re-writing the array in a different form, which is tedious and error prone.

    opened by BrianKenny 0
Owner
Jason Fieldman
Jason Fieldman
An Impressive Auto Layout DSL for iOS, tvOS & OSX. & It is written in pure swift.

KVConstraintKit KVConstraintKit is a DSL to make easy & impressive Auto Layout constraints on iOS, tvOS & OSX with Swift Installation Using CocoaPods

Keshav Vishwkarma 90 Sep 1, 2022
Yet Another Swift Auto Layout DSL

FormationLayout Documentation FormationLayout is the top level layout class for one root view. FormationLayout takes a UIView as its rootView. transla

Evan Liu 53 Mar 31, 2022
Swifty DSL for programmatic Auto Layout in iOS

WWLayout Easy to write auto layout constraints, with minimal extensions to standard namespaces. Feature Highlights Easy to use, readable API Backwards

WW Tech 49 Oct 2, 2022
Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. [iOS/macOS/tvOS/CALayer]

Extremely Fast views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainabl

layoutBox 2.1k Dec 22, 2022
Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast

Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. [iOS/macOS/tvOS/CALayer]

layoutBox 2.1k Jan 2, 2023
MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.

MisterFusion MisterFusion makes more easier to use AutoLayout in Swift & Objective-C code. Features Simple And Concise Syntax Use in Swift and Objecti

Taiki Suzuki 316 Nov 17, 2022
Auto Layout (and manual layout) in one line.

Auto Layout (and manual layout) in one line. Quick Look view.bb.centerX().below(view2).size(100) It’s equivalent to iOS 9 API: view.centerXAnchor.cons

Javier Zhang 74 Oct 19, 2022
Auto Layout made easy with the Custom Layout.

Auto Layout made easy with the Custom Layout. Getting started CocoaPods CocoaPods is a dependency manager for Cocoa projects. You can install it with

Malith Nadeeshan 1 Jan 16, 2022
Lightweight Swift framework for Apple's Auto-Layout

I am glad to share with you a lightweight Swift framework for Apple's Auto-Layout. It helps you write readable and compact UI code using simple API. A

null 349 Dec 20, 2022
The ultimate API for iOS & OS X Auto Layout — impressively simple, immensely powerful. Objective-C and Swift compatible.

The ultimate API for iOS & OS X Auto Layout — impressively simple, immensely powerful. PureLayout extends UIView/NSView, NSArray, and NSLayoutConstrai

PureLayout 7.6k Jan 6, 2023
Auto Layout In Swift Made Easy

Swiftstraints Swiftstraints can turn verbose auto-layout code: let constraint = NSLayoutConstraint(item: blueView, attr

null 119 Jan 29, 2022
Lightweight declarative auto-layout framework for Swift

SwiftyLayout SwiftyLayout is a framework that allows to describe layout constraints (ie NSLayoutConstraint) as a simple mathematical formula in a Swif

Hisakuni Fujimoto 15 Nov 7, 2017
Minimal Auto Layout in Swift

Restraint Restraint is a very very small library to help make your use of NSLayoutConstraint in Swift more legible & declarative. Like programmatic vi

The Puffin Supply Project 80 Aug 23, 2022
Swift microframework for declaring Auto Layout constraints functionally

Relayout Relayout is a Swift microframework to make using Auto Layout easier with static and dynamic layouts. Why? If you want to build a UI using App

Steve Streza 560 Nov 19, 2022
SuperLayout is a Swift library that makes using Auto Layout a breeze.

SuperLayout is a library that adds a few custom operators to Swift that makes using the amazing NSLayoutAnchor API for Auto Layout a breeze. SuperLayo

Lionheart Software 53 Oct 12, 2022
Written in pure Swift, QuickLayout offers a simple and easy way to manage Auto Layout in code.

QuickLayout QuickLayout offers an additional way, to easily manage the Auto Layout using only code. You can harness the power of QuickLayout to align

Daniel Huri 243 Oct 28, 2022
Declarative Auto Layout in Swift, clean and simple

Tails Tails is a take on declarative Auto Layout. If you don't like typing (like me), it might be your kind of thing! Tails is written in Swift and cu

Nick Tymchenko 17 Jan 31, 2019
Auto Layout made easy

EasyPeasy is a Swift framework that lets you create Auto Layout constraints programmatically without headaches and never ending boilerplate code. Besi

Carlos Vidal 1.9k Dec 23, 2022
TinyConstraints is the syntactic sugar that makes Auto Layout sweeter for human use.

TinyConstraints is the syntactic sugar that makes Auto Layout sweeter for human use. Features Pure Swift 5 sweetness. Everything you can do with Auto

Robert-Hein Hooijmans 3.8k Jan 5, 2023