{"id":13465830,"url":"https://github.com/jmfieldman/Mortar","last_synced_at":"2025-03-25T21:30:33.117Z","repository":{"id":62448015,"uuid":"50786024","full_name":"jmfieldman/Mortar","owner":"jmfieldman","description":"A compact but full-featured Auto Layout DSL for Swift","archived":false,"fork":false,"pushed_at":"2023-02-11T22:47:44.000Z","size":3329,"stargazers_count":83,"open_issues_count":1,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T19:08:51.236Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmfieldman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-01-31T17:28:49.000Z","updated_at":"2023-08-22T03:19:35.000Z","dependencies_parsed_at":"2022-11-01T23:05:40.156Z","dependency_job_id":"c2ae997d-5bd9-4de4-b32b-2764666bb145","html_url":"https://github.com/jmfieldman/Mortar","commit_stats":{"total_commits":178,"total_committers":7,"mean_commits":"25.428571428571427","dds":"0.061797752808988804","last_synced_commit":"badaa7acaf6cf294b060fae1ba2413b0c72d1b9e"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FMortar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FMortar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FMortar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FMortar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmfieldman","download_url":"https://codeload.github.com/jmfieldman/Mortar/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245546728,"owners_count":20633218,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T15:00:36.014Z","updated_at":"2025-03-25T21:30:32.558Z","avatar_url":"https://github.com/jmfieldman.png","language":"Swift","funding_links":[],"categories":["Libs","Layout [🔝](#readme)"],"sub_categories":["Layout"],"readme":"![Mortar](/Development/Art/Banner.png)\n\n![Swift 5.0](https://img.shields.io/badge/Swift_5.0-latest-orange.svg?style=flat)\n![Swift 4.2](https://img.shields.io/badge/Swift_4.2-%7E%3E%201.5-orange.svg?style=flat)\n![Swift 4.0](https://img.shields.io/badge/Swift_4.0-%7E%3E%201.4-orange.svg?style=flat)\n\nMortar allows you to create Auto Layout constraints using concise, simple code statements.\n\nUse this:\n\n```swift\nview1.m_right |=| view2.m_left - 12.0\n```\n\nInstead of:\n\n```swift\naddConstraint(NSLayoutConstraint(\n    item:        view1,\n    attribute:  .right,\n    relatedBy:  .equal,\n    toItem:      view2,\n    attribute:  .left,\n    multiplier:  1.0,\n    constant:   -12.0\n))\n```\n\nOther examples:\n\n```swift\n/* Set the size of three views at once */\n[view1, view2, view3].m_size |=| (100, 200)\n\n/* Pin a 200px high, same-width view at the bottom of a container */\n[view.m_sides, view.m_bottom, view.m_height] |=| [container, container, 200]\n\n/* VFL syntax */\nview1 |\u003e\u003e viewA || viewB[==44] | 20 | viewC[~~2]\n```\n\n#### Updating from a version prior to v1.1? Read this!\n\n\u003e A change in the default Mortar constraint priority took effect in v1.1.  Please read the README_DEFAULTS.md file for more information.\n\n# Why?\n\nYes, there are many Auto Layout DSLs to choose from.  Mortar was created to fill perceived weaknesses in other offerings:\n\n* Mortar does not use blocks/closures like SnapKit or Cartography. These are distracting and ugly when coding constraints for controllers with many views.\n\n* 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.\n\n* 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.\n\n* Mortar supports implicit property matching (other frameworks require declared properties on both sides of the statement.)\n\n* Mortar supports implicit tuple processing (you don't need to call out a tuple as a specific element like ```CGRect``` or ```CGSize```).\n\n* Mortar supports multi-view alignment/constraints in a single line.\n\n* Mortar supports a robust compile-time VFL syntax that uses views directly (instead of dictionary lookups).\n\n* Mortar has additional goodies, like the ```|+|``` operator to visually construct view hierarchies rather than using tons of sequential calls to ```addSubview()```.\n\n* Mortar lets you construct a view hierarchy and supply layout constraints in a\nsingle imperative expression.\n\n# Installing\n\n### Swift Package Manager (Preferred)\n\nAdd Mortar to your Package.swift dependency list:\n\n```swift\n.package(url: \"https://github.com/jmfieldman/Mortar.git\", from: \"2.0.0\")\n```\n\n### Cocoapods\n\n\u003e Notice: Cocoapods support has been deprecated; the last supported Mortar version is 1.7.0\n\nYou can install Mortar by adding it to your [CocoaPods](http://cocoapods.org/) ```Podfile```:\n\n```ruby\npod 'Mortar'\n```\n\nIf you would like to use the Mortar VFL language:\n\n```ruby\npod 'Mortar/MortarVFL'\n```\n\nOr you can use a variety of ways to include the ```Mortar.framework``` file from this project into your own.\n\n# Swift Version Support\n\nVersion 2.0.0, using the Swift Package Manager, has been tested with Xcode 14.2 and Swift 5.7.\n\n\u003e This README reflects the updated syntax and constants used in the Swift 3 Mortar release.\n\u003e For the Swift 2.x documentation, refer to the ```README_SWIFT2.md``` file.\n\n```ruby\npod 'Mortar', '~\u003e 1.6'  # Swift 5.0\npod 'Mortar', '~\u003e 1.5'  # Swift 4.2\npod 'Mortar', '~\u003e 1.4'  # Swift 4.0\npod 'Mortar', '~\u003e 1.3'  # Swift 3.1\n```\n\n#### Disabling MortarCreatable (Cocoapods, Legacy Swift)\n\nThe default implementation of Mortar declares a MortarCreatable protocol (create), which in legacy versions of\nswift causes problems with classes that do not expose the default init() method.\n\nIf you targetting a very old version of Swift, you can use:\n\n```ruby\npod 'Mortar/Core_NoCreatable'\npod 'Mortar/MortarVFL_NoCreatable'\n```\n\n# Usage\n\nMortar does not require closures of any kind.  The Mortar operators (```|=|```, ```|\u003e|``` and ```|\u003c|```) instantiate and return constraints that are activated by default.\n\nMortar will set ```translatesAutoresizingMaskIntoConstraints``` to ```false``` for every view declared on the left side of an operator.\n\n\n### Equal, Less or Greater?\n\nThere are three mortar operators:\n\n```swift\nview1.m_width |=| 40                // Equal\nview1.m_width |\u003e| view2.m_width     // Greater than or equal\nview1.m_size  |\u003c| (100, 100)        // Less than or equal\n```\n\n### Attributes\n\nMortar supports all of the standard layout attributes:\n\n* ```m_left```\n* ```m_right```\n* ```m_top```\n* ```m_bottom```\n* ```m_leading```\n* ```m_trailing```\n* ```m_width```\n* ```m_height```\n* ```m_centerX```\n* ```m_centerY```\n* ```m_baseline```\n\nAnd iOS/tvOS spceific attributes:\n\n* ```m_firstBaseline```\n* ```m_leftMargin```\n* ```m_rightMargin```\n* ```m_topMargin```\n* ```m_bottomMargin```\n* ```m_leadingMargin```\n* ```m_trailingMargin```\n* ```m_centerXWithinMargin```\n* ```m_centerYWithinMargin```\n\nIt also supports composite attributes:\n\n* ```m_sides -- (left, right)```\n* ```m_caps -- (top, bottom)```\n* ```m_size -- (width, height)```\n* ```m_center -- (centerX, centerY)```\n* ```m_cornerTL -- (top, left)```\n* ```m_cornerTR -- (top, right)```\n* ```m_cornerBL -- (bottom, left)```\n* ```m_cornerBR -- (bottom, right)```\n* ```m_edges -- (top, left, bottom, right)```\n* ```m_frame -- (top, left, width, height)```\n\n### Implicit Attributes\n\n_Mortar will do its best to infer implicit attributes!_\n\nThe ```m_edges``` attribute is implied when no attributes are declared on either side:\n\n```swift\nview1.m_edges |=| view2.m_edges     // These two lines\nview1         |=| view2             // are equivalent.\n```\n\nIf an attribute is declared on one side, it is implied on the other:\n\n```swift\nview1.m_top   |\u003e| view2             // These two lines\nview1         |\u003e| view2.m_top       // are equivalent.\n```\n\nYou are required to put attributes on both sides if they are not the same:\n\n```swift\nview1.m_top   |\u003c| view2.m_bottom\n```\n\n### Using Layout Guides\n\nOn iOS you can access the layout guides of a ```UIViewController```.  An example from inside ```viewDidLoad()``` that puts a view just below the safe top:\n\n```swift\n// Super useful when trying to position views inside a navigation/tab controller!\nview1.m_top   |\u003c| self.m_safeTop\n```\n\nThere is also a new ```UIViewController``` property ```m_safeRegion``` to help align views to the safe region of a controller's view.  *To use this property you must have the MortarVFL extension installed.*\n\n```swift\n// Center a view inside the safe region of a UIViewController that is a child of\n// a navigation controller or tab controller\ntextField.m_center |=| self.m_safeRegion\n```\n\nUsing ```m_safeRegion``` 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.\n\n\n### Multipliers and Constants\n\nAuto 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.\n\n```swift\nview1.m_size  |=| view2.m_size  * 2         // Multiplier\nview1.m_left  |\u003e| view2.m_right + 20        // Constant\nview1         |\u003c| view2.m_top   * 1.4 + 20  // Both -- m_top is implied on the left\n```\n\nYou can also set attributes directly to constants:\n\n```swift\nview1.m_width |=| 100                // Single-dimension constants\nview1.m_size  |=| 150.0              // Set multiple dimensions to the same constant\nview1.m_size  |\u003e| (200, 50)          // Set multiple dimensions to a tuple value\nview1.m_frame |\u003c| (0, 0, 50, 100)    // Four-dimension tuples supported\n```\n\nArithmetic can be done using tuples for multi-dimension attributes:\n\n```swift\nview1.m_size  |=| view2.m_size + (50, 30)\nview1.m_size  |\u003e| view2.m_size * (2, 3) + (10, 10)\n```\n\nThe special inset operator ```~``` operates on multi-dimension attributes:\n\n```swift\nview1         |=| view2.m_edges ~ (20, 20, 20, 20)  // view1 is inset by 20 points on each side\n```\n\n### Attributes in Tuples\n\nYou are allowed to put attributes inside of tuples:\n\n```swift\nview1.m_size  |=| (view2.m_width, 100)\n```\n\n### Multiple Simultaneous Constraints\n\nMultiple constraints can be created using arrays:\n\n```swift\nview1 |=| [view2.m_top, view3.m_bottom, view4.m_size]\n\n/* Is equivalent to: */\nview1 |=| view2.m_top\nview1 |=| view3.m_bottom\nview1 |=| view4.m_size\n```\n\n```swift\n[view1, view2, view3].m_size |=| (100, 200)\n\n/* Is equivalent to: */\n[view1.m_size, view2.m_size, view3.m_size] |=| (100, 200)\n\n/* Is equivalent to: */\nview1.m_size |=| (100, 200)\nview2.m_size |=| (100, 200)\nview3.m_size |=| (100, 200)\n```\n\nThis might be a convenient way to align an array of views, for example:\n\n```swift\n[view1, view2, view3].m_centerY |=| otherView\n[view1, view2, view3].m_height  |=| otherView\n```\n\nIf you put arrays on both sides of the constraint, it will only constrain elements at the same index.  That is:\n\n```swift\n[view1.m_left, view2, view3] |=| [view4.m_right, view5, view6]\n\n/* Is equivalent to: */\nview1.m_left |=| view4.m_right\nview2        |=| view5\nview3        |=| view6\n```\n\nYou 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:\n\n```swift\n[view.m_sides, view.m_bottom, view.m_height] |=| [container, container, 200]\n```\n\n### Priority\n\nYou can assign priority to constraints using the ```!``` operator.  Valid priorities are:\n\n* ```.low```, ```.medium```, ```.high```, ```.required```\n* Any ```UILayoutPriority``` value\n\n```swift\nv0 |=| self.container.m_height\nv1 |=| self.container.m_height ! .low\nv2 |=| self.container.m_height ! .medium\nv3 |=| self.container.m_height ! .high\nv4 |=| self.container.m_height ! .required\nv5 |=| self.container.m_height ! 300\n```\n\nYou can also put priorities inside tuples or arrays:\n\n```swift\nview1        |=| [view2.m_caps   ! .high, view2.m_sides      ! .low]  // Inside array\nview1.m_size |=| (view2.m_height ! .high, view2.m_width + 20 ! .low)  // Inside tuple\n```\n\n### Default Priority\n\n\u003e Defaults have changed in Mortar v1.1; See README_DEFAULTS.md if you are updating.\n\nBy 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\nmay want large batches of constraints to have a different priority, and it is messy to include something like\n```! .medium``` after every constraint.\n\nYou can change the global base default value by using ```set```:\n\n```swift\nMortarDefault.priority.set(base: .medium)\n```\n\nYou can use this in the ```AppDelegate``` to change the app-wide default constraint priority.\n\nBecause this can only be changed on the main thread, it is safe to call just before your\nlayout code.  Keep in mind it will affect all future Mortar contraints!  If you are adjusting\nthe default for a single layout section, it is usually wiser to use the stack mechanism\nto change the default priority used in a frame of code:\n\n```swift\nMortarDefault.priority.push(.low)\n\nv1 |=| v2 // Given priority .low automatically\n...\n\nMortarDefault.priority.pop()\n```\n\nYou may only call the push/pop methods on the main thread, and Mortar will raise an exception if you do not\nproperly balance your pushes and pops.\n\n### Change Priority\n\nYou can change the priority of a ```MortarConstraint``` or ```MortarGroup``` by calling the ```changePriority``` method.  This takes either a ```MortarLayoutPriority``` enum, or a ```UILayoutPriority``` value:\n\n```swift\nlet c = view1 |=| view2 ! .low     // Creates 4 low-priority constraints (1 per edge)\nc.changePriority(to: .high)        // Sets all 4 constraints to high priority\n```\n\nRemember that you can't switch to or from ```Required``` from any other priority level (this is an Auto Layout limitation.)\n\n\n### Create Deactivated Constraints\n\nYou 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:\n\n```swift\nlet constraint = view1 |=| view2 ~~ .deactivated\n\n// Later on, it makes more semantic sense to call .activate():\nconstraint.activate()\n\n// Even though this is functionally equivalent:\nconstraint ~~ .activated\n\n// It works with groups too:\nlet group = [\n    view1 |=| view2\n    view3 |=| view4\n] ~~ .deactivated\n\n```\n\n# Keeping Constraint References\n\nThe 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).\n\nYou can capture a ```MortarConstraint``` for later reference:\n\n```swift\nlet constraint = view1.m_top |=| view2.m_bottom\n```\n\nThe raw ```NSLayoutConstraint``` elements can be accessed through the ```layoutConstraints``` accessor:\n\n```swift\nlet mortarConstraint = view1.m_top |=| view2.m_bottom\nfor rawLayoutConstraint in mortarConstraint.layoutConstraints {\n    ...\n}\n```\n\nYou can create an entire group of constraints:\n\n```swift\nlet group = [\n    view1.m_origin |=| view2,\n    view1.m_size   |=| (100, 100)\n]\n```\n\nMortar includes a convenient typealias to refer to arrays of ```MortarConstraint``` objects:\n\n```swift\npublic typealias MortarGroup = [MortarConstraint]\n```\n\nYou can now activate/deactivate constraints:\n\n```swift\nlet constraint = view1.m_top |=| view2.m_bottom\n\nconstraint.activate()\nconstraint.deactivate()\n\nlet group = [\n    view1.m_origin |=| view2,\n    view1.m_size   |=| (100, 100)\n]\n\ngroup.activate()\ngroup.deactivate()\n```\n\n### Replacing Constraints and Groups of Constraints\n\nConstraints and groups have a ```replace``` method that deactives the target and activates the parameter:\n\n```swift\nlet constraint1 = view1.m_sides |=| view2\nlet constraint2 = view1.m_width |=| view2 ~~ .deactivated\n\nconstraint1.replace(with: constraint2)\n\nlet group1 = [\n    view1.m_sides |\u003c| view2,\n    view1.m_caps  |\u003e| view2,\n]\n\nlet group2 = [\n    view1.m_width |=| view2\n] ~~ .deactivated\n\ngroup1.replace(with: group2)\n```\n\n## Compression Resistance and Content Hugging\n\nMortar provides some shorthand properties to adjust a view's compression resistance and content hugging priorities:\n\n```swift\n// Set both horizontal and vertical compression resistance priority simultaneously:\nview1.m_compResist = 1\n\n// Set horizontal and vertical compression resistance independently:\nview1.m_compResistH = 300\nview1.m_compResistV = 800\n\n// Set both horizontal and vertical content hugging priority simultaneously:\nview1.m_hugging = 1\n\n// Set horizontal and vertical content hugging independently:\nview1.m_huggingH = 300\nview1.m_huggingV = 800\n```\n\nYou can get the horizontal and vertical values independently, but not together:\n\n```swift\n// These getters are fine:\nlet c1 = view1.m_compResistH\nlet c2 = view1.m_compResistV\nlet h1 = view1.m_huggingH\nlet h2 = view1.m_huggingV\n\n// These getters raise exceptions:\nlet cr = view1.m_compResist\nlet hg = view1.m_hugging\n```\n\n# MortarVFL\n\nMortar supports a VFL language that is roughly equivalent to Apple's own [Auto Layout VFL langauge](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html).  The primary advanges are:\n\n* You can use it directly with existing Mortar attribute support\n* Views are referenced directly (instead of using dictionaries) for compile-time checking\n* Full weight-based support for relative sizing\n* More concise: Operator-based instead of function/string-based\n\nMortarVFL 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.\n\n```ruby\npod 'Mortar/MortarVFL'\n```\n\n## MortarVFL Internal Composition\n\nThe 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:\n\n```swift\nviewA | viewB[==viewA] || viewC[==40] | 30 | viewD[~~1] | ~~1 | viewE[~~2]\n\n// viewA has a size determinde by its intrinsic content size\n// viewA is separated from viewB by 0 points (| operator)\n// viewB has a size equal to viewA\n// viewB is separated from viewC by the default padding (8 points; || operator)\n// viewC has a fixed size of 40\n// viewC is separated from viewD by a space of 30 points\n// viewD has a weighted size of 1\n// viewD is separated fom viewE by a weighted space of 1\n// viewE has a weighted size of 2\n```\n\nVFL nodes:\n* Represent either whitespace, one view, or multiple views\n* Have either fixed spacing or weighted spacing\n\nNodes are separated by either a ```|``` or ```||``` operator.\n\nThe ```|``` 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).\n\nThe ```||``` operator separates nodes by the default padding (8 points).\n\nNodes 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.\n\nMortarVFL will throw an error if you have cyclic view references, e.g. ```viewA[==viewB] | viewB[==viewA]```\n\n### Arrays in a Node\n\nAs an advanced technique, you can use an array of views in a node.  It would look something like this:\n\n```swift\nviewA || [viewB, viewC, viewD][==40] || viewE\n```\n\nThis 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.\n\n\n## Capture\n\nMortarVFL statements must be captured on at least one end by a view attribute.  These captures look something like:\n\n```swift\n// viewB and viewC will take equal width between the\n// right edge of viewA and the left edge of viewD\nviewA.m_right |\u003e viewB[~~1] | viewC[~~1] \u003c| viewD.m_left\n\n// viewB and viewC will be equal width between the\n// left/right edges of viewA, inset by 8pt padding\n// and separated by 40pts.\nviewA ||\u003e\u003e viewB[~~1] | 40 | viewC[~~1]\n```\n\nMortarVFL support horizontal and vertical spacing in a similar manner.  The horizontal operators use the ```\u003e``` character while the vertical operators use the ```^``` character.  Otherwise they act similarly.  For example, the vertical version of the above statement would be:\n\n```swift\n// viewB and viewC will take equal height between the\n// bottom edge of viewA and the top edge of viewD\nviewA.m_bottom |^ viewB[~~1] | viewC[~~1] ^| viewD.m_top\n```\n\nMortar will make sure your operators are compatible with the attributes you've selected.  For example, using ```|\u003e``` with ```m_top``` would be an axis mismatch and raise an exception.\n\n### Implicit Capture Attributes\n\nIf you don't provide attributes on the capture terminals, Mortar will derive them based on the axis and position:\n\n```swift\n// These are equivalent:\nviewA.m_left |\u003e viewB | viewC \u003c| viewD.m_right\nviewA        |\u003e viewB | viewC \u003c| viewD\n\n// These are equivalent:\nviewA.m_top  |^ viewB | viewC ^| viewD.m_bottom\nviewA        |^ viewB | viewC ^| viewD\n```\n\n***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.\n\n### Implicit Surround\n\nIf 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.\n\nThe surround operators use either ```\u003e\u003e``` or ```^^```:\n\n```swift\n// viewB and viewc will be equal width between the\n// left/right edges of viewA, inset by 8pt padding\n// and separated by 40pts.\nviewA ||\u003e\u003e viewB[~~1] | 40 | viewC[~~1]\n\n// viewB will be twice as tall as viewC; both will be between\n// the top/bottom edges of viewA.\nviewA |^^ viewB[~~2] | viewC[~~1]\n\n// Using m_visibleRegion is helpful for layouts in child view controllers\n// to get views laid out inside the visible region, not under nav/tab bars\nself.m_visibleRegion ||^^ viewA | viewB | viewC\n```\n\n### Single-Ended Statements\n\nUp until now, all of the examples have shown statements bordered by two attributes (left and right, top and bottom).\n\nFor 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.\n\nFor 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.\n\nSingle-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 ```\u003c!```, ```\u003c!!```,  ```^!``` or ```^!!```.\n\n```swift\n// viewB will be placed at the right edge of viewA and be 44pts wide.\n// viewC will be placed 8pts (padding) right of viewB and will be 88pts wide.\nviewA.m_right |\u003e viewB[==44] || viewC[==88]\n\n// viewC will be placed at the left edge of viewA and be 88pts wide.\n// viewB will be placed 8pts (padding) left of viewC and will be 44pts wide.\nviewB[==44] || viewC[==88] \u003c! viewA.m_left\n\n// viewB will be placed at the bottom edge of viewA and be 44pts high.\n// viewC will be placed 8pts below viewB and respect its intrinsic content height.\nviewA.m_bottom |^ viewB[==44] || viewC\n\n// viewC will be placed at the top edge of viewA and be 88pts high.\n// viewB will be placed 8pts above viewC and will be 44pts high.\nviewB[==44] || viewC[==88] ^! viewA.m_top\n```\n\nAgain, 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: ```|\u003e```\n\n## Examples\n\nThere are several examples of MortarVFL in the Examples/MortarVFL project.\n\n# Visual View Hierarchy Creation\n\nMortar 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.\n\nNow this:\n\n```swift\nself.view.addSubview(backgroundView)\nself.view.addSubview(myCoolPanel)\nmyCoolPanel.addSubview(nameLabel)\nmyCoolPanel.addSubview(nameField)\n```\n\nTurns into:\n\n```swift\n    self.view |+| [\n        backgroundView,\n        myCoolPanel |+| [\n            nameLabel,\n            nameField\n        ]\n    ]\n```\n\nAlternatively, 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:\n\n```swift\n    self.view |^| [\n        myCoolPanel |^| [      // myCoolPanel is added second and\n            nameField,         // is therefore on top of backgroundView\n            nameLabel\n        ],\n        backgroundView\n    ]\n```\n\n### Initializing NSObject at Creation\n\nMortar extends `NSObject` with the `create` class function.  This class function performs a parameter-less\ninstantiation of the class, and passes the new instance into the provided closure.  This allows you to configure\nan instance at creation-time, which is really nice for compartmentalizing view configuration.\n\nAs you can see in the below example, configuration of the view is separated from the code needed to attach it\nto the view controller hierarchy and layout.\n\n```swift\nclass MyController: UIViewController {\n\n    // Instantiation/configuration\n    let myLabel = UILabel.create {\n        $0.text          = \"Some Text\"\n        $0.textAlignment = .center\n        $0.textColor     = .red\n    }\n\n    // UIViews can use the immediate init block\n    let myButton = UIButton {\n        $0.setTitle(\"Hello\", for: .normal)\n    }\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        // Hierarchy\n        self.view |+| [\n            myLabel,\n            myButton\n        ]\n\n        // Layout\n        myLabel.m_top     |=| self.view\n        myLabel.m_centerX |=| self.view\n        myButton.m_width  |=| myLabel\n    }\n}\n```\n\n### Combining Hierarchy and Layout\n\nOne of the historical problems with combining hierarchy and layout into a\nsingle function (like SwiftUI) is that UIKit requires two views to be in\nthe same view hierarchy *before* a constrait is activated.\n\nThis prevented you from doing something like:\n\n```swift\nview1 |+| [\n    UILabel.create {\n        // Crash here, because the newly created UILabel is not\n        // a subview of view1 until *after* the top-level |+| is\n        // executed.\n        $0.m_width |=| view1\n    }\n]\n```\n\nAs of version 2.0.0, Mortar understands how to defer constraint activation\nwhile the hierarchy is being created:\n\n```swift\nview1 |+| [\n    UILabel.create {\n        // This is OK in Mortar 2.0.0; the constraint will not\n        // activate until after the outer-most |+| executes.\n        $0.m_width |=| view1\n    }\n]\n```\n\nThat is, any time the `|+|` or `|^|` operators are in progress, or you use the\nnew `.addSubviews` or `.addArrangedSubviews` result builders, Mortar knows *not*\nto activate any constraint assignments until after the outer-most hierarchy\nassignment is completed.\n\nMortar 2.0.0 now also offers result builders as the right hand side of the\nadd subview operators. The result builders take the left-side view in as a\nblock parameter, which lets children easily reference anonymous parents:\n\n```swift\nview |+| { view in\n    UIStackView {\n        $0.spacing = 1\n        $0.axis = .vertical\n        $0.m_width |=| view\n    } |+| { stack in\n        UILabel {\n            $0.text = viewModel.helloText\n            $0.m_height |=| stack\n        }\n        UILabel {\n            $0.text = \"World\"\n            $0.m_height |=| stack\n            $0.reactive.text \u003c~ viewModel.worldText\n        }\n        UIButton {\n            $0.reactive.pressed = viewModel.pressed\n        }\n    }\n]\n```\n\nCombining this with a reactive framework like ReactiveSwift can get you\nnearly all the way to a single-expression view definition, even for large\ncustom layouts that can leverage stack views properly.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2FMortar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmfieldman%2FMortar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2FMortar/lists"}