{"id":30499939,"url":"https://github.com/codeface-io/getlaid","last_synced_at":"2025-10-04T19:04:20.923Z","repository":{"id":56912704,"uuid":"148426017","full_name":"codeface-io/GetLaid","owner":"codeface-io","description":"The Most Readable \u0026 Concise AutoLayout Swift Code","archived":false,"fork":false,"pushed_at":"2022-11-20T14:42:34.000Z","size":852,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-31T15:32:33.512Z","etag":null,"topics":["appkit","autolayout","clean-code","cleancode","cocoapod","ios","layout","macos","nslayoutconstraint","nslayoutguide","nsview","programmatic","purelayout","swift","uikit","uilayoutguide","uiview"],"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/codeface-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-12T05:27:22.000Z","updated_at":"2025-02-23T12:44:33.000Z","dependencies_parsed_at":"2022-08-21T03:20:22.138Z","dependency_job_id":null,"html_url":"https://github.com/codeface-io/GetLaid","commit_stats":null,"previous_names":["codeface-io/getlaid","flowtoolz/getlaid"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/codeface-io/GetLaid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FGetLaid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FGetLaid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FGetLaid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FGetLaid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeface-io","download_url":"https://codeload.github.com/codeface-io/GetLaid/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FGetLaid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278358566,"owners_count":25973972,"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","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["appkit","autolayout","clean-code","cleancode","cocoapod","ios","layout","macos","nslayoutconstraint","nslayoutguide","nsview","programmatic","purelayout","swift","uikit","uilayoutguide","uiview"],"created_at":"2025-08-25T07:22:28.761Z","updated_at":"2025-10-04T19:04:20.911Z","avatar_url":"https://github.com/codeface-io.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GetLaid\n\n![badge-pms] ![badge-languages] ![badge-platforms] ![badge-mit]\n\nGetLaid is a lean framework for laying out complex UIs through short readable code.\n\n* [Why Oh Why?](#why-oh-why)\n* [Install](#install)\n* [Add Subviews and Layout Guides](#add-subviews-and-layout-guides)\n* [Constrain a Position](#constrain-a-position)\n* [Constrain Multiple Positions](#constrain-multiple-positions)\n* [Constrain a Dimension](#constrain-a-dimension)\n* [Constrain Both Dimensions](#constrain-both-dimensions)\n* [Constrain to the Parent](#constrain-to-the-parent)\n* [Constrain to the Safe Area on iOS](#constrain-to-the-safe-area-on-ios)\n* [System Spacing on iOS and tvOS](#system-spacing-on-ios-and-tvos)\n\n## Why Oh Why?\n\n* :white_check_mark: Readability\n    - The syntax is close to natural language instead of technically fancy.\n    - All constraining takes the form `source.constrain(to: target)`.\n    - The operator `\u003e\u003e` can add further clarity: `source \u003e\u003e target`\n* :white_check_mark: Brevity\n    - Code lines are super short and involve few function arguments.\n    - A single code line can do a lot, via combined targets like `allButTop` and `size`.\n* :white_check_mark: Simplicity / Flexibility\n    - Simple consistent systematic design: Understand 1 thing to do everything.\n    - Seemless coverage of parent views, safe areas and system spacings\n* :white_check_mark: Easy Advanced Layouting\n    - Modify any constrain target with `offset(CGFLoat)`, `min`, `max` and `at(_ factor: CGFloat)`.\n    - Chain target modifications together: `item1 \u003e\u003e item2.size.at(0.5).min`\n* :white_check_mark: Compatibility\n    - Works on iOS, tvOS and macOS.\n    - Works on [UILayoutGuide](https://developer.apple.com/documentation/uikit/uilayoutguide) and [NSLayoutGuide](https://developer.apple.com/documentation/appkit/nslayoutguide) just as well as on views.\n\n### Why Not Interface Builder?\n\nWell, that [would be insane](https://www.flowtoolz.com/2019/09/27/the-reasons-for-why-i-hate-xcode-interface-builder.html).\n\n### Why AutoLayout Wrappers?\n\nProgrammatic AutoLayout without any such frameworks was never hard. It's all about creating objects of `NSLayoutConstraint`, which has only one [powerful initializer](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526954-init).\n\nSince iOS/tvOS 9.0 and macOS 10.11, we also have [`NSLayoutAnchor`](https://developer.apple.com/documentation/uikit/nslayoutanchor), which adds a native abstraction layer on top of `NSLayoutConstraint`, further reducing the need for any AutoLayout wrappers at all.\n\nAt this point, all an AutoLayout wrapper can do is to make layout code even more meaningful, readable and succinct at the point of use. GetLaid does exactly that.\n\n### Why Not Other AutoLayout Wrappers?\n\nModern AutoLayout wrappers like [SnapKit](https://github.com/SnapKit/SnapKit) are almost too clever for the simple task at hand. The first example from the SnapKit README:\n\n~~~swift\nbox.snp.makeConstraints { (make) -\u003e Void in\n    make.width.height.equalTo(50)\n    make.center.equalTo(self.view)\n}\n~~~\n\nClassic AutoLayout wrappers like [PureLayout](https://github.com/PureLayout/PureLayout), have easier syntax but are still wordy:\n\n~~~swift\nbox.autoSetDimensions(to: CGSize(width: 50, height: 50))\nbox.autoCenterInSuperView()\n~~~\n\nGetLaid trims AutoLayout further down to the essence. Just read the operator `\u003e\u003e` as \"constrain to\":\n\n~~~swift\nbox \u003e\u003e 50\nbox \u003e\u003e view.center\n~~~\n\nSo, which is prettier, mh? If you can spare fancyness but appreciate readability, GetLaid might be for you.\n\nHere is also a [richer comparison](Documentation/comparison_to_alternatives.md) of how layout code looks with GetLaid and its alternatives.\n\n## Install\n\nWith the [**Swift Package Manager**](https://github.com/apple/swift-package-manager/tree/master/Documentation#swift-package-manager), you can just add the GetLaid package [via Xcode](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) (11+).\n\nOr you manually adjust the [Package.swift](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#create-a-package) file of your project:\n\n~~~swift\n// swift-tools-version:5.1\nimport PackageDescription\n\nlet package = Package(\n    name: \"MyApp\",\n    dependencies: [\n        .package(url: \"https://github.com/flowtoolz/GetLaid.git\",\n                 .upToNextMajor(from: \"3.0.0\"))\n    ],\n    targets: [\n        .target(name: \"MyAppTarget\",\n                dependencies: [\"GetLaid\"])\n    ]\n)\n~~~\n\nThen run `$ swift build` or `$ swift run`.\n\nWith [**Cocoapods**](https://cocoapods.org), adjust your [Podfile](https://guides.cocoapods.org/syntax/podfile.html):\n\n```ruby\ntarget \"MyAppTarget\" do\n  pod \"GetLaid\", \"~\u003e 3.0\"\nend\n```\n\nThen run `$ pod install`.\n\nFinally, in your **Swift** files:\n\n```swift\nimport GetLaid\n```\n\n## Add Subviews and Layout Guides\n\nThe generic function `addForAutoLayout` adds a subview and prepares it for AutoLayout. It returns the subview it takes as its exact type. Use this function to add subviews:\n\n~~~swift\nclass List: UIView {\n    // ... other code, including call to addSubviews() ...\n\n    func addSubviews() {\n        addForAutoLayout(header) \u003e\u003e allButBottom  // add header to the top\n    }\n    private let header = Header()\n}\n~~~\n\nIf you don't use `addForAutoLayout`, remember to set `translatesAutoresizingMaskIntoConstraints = false` on the views you incorporate in AutoLayout.\n\nThere's also a helper function for adding a new layout guide to a view:\n\n~~~swift\nlet guide = view.addLayoutGuide()\n~~~\n\n## Constrain a Position\n\nYou would always call `constrain(to:)` on exactly the thing you want to constrain. And you can always replace that function with the shorthand operator `\u003e\u003e`, which we'll do in the examples. These lines are equivalent :\n\n```swift\nview1.top.constrain(to: view2.lastBaseline)\nview1.top \u003e\u003e view2.lastBaseline\n```\n\nAll layout attributes can be used in that way, while baselines are not available on layout guides.\n\nIf source and target refer to the same attribute, you may omit the attribute on one side. These are equivalent:\n\n```swift\nitem1.left \u003e\u003e item2.left\nitem1.left \u003e\u003e item2\nitem1 \u003e\u003e item2.left\n```\n\nYou may modify the constrain target and also chain these modifications:\n\n```swift\nitem1 \u003e\u003e item2.left.offset(8)\nitem1 \u003e\u003e item2.left.min            // \u003e= item2.left\nitem1 \u003e\u003e item2.left.max            // \u003c= item2.left\nitem1 \u003e\u003e item2.left.at(0.5)        // at 0.5 of item2.left\nitem1 \u003e\u003e item2.left.min.offset(8)\n```\n\n## Constrain Multiple Positions\n\nYou may constrain multiple positions at once:\n\n```swift\nitem1 \u003e\u003e item2.allButTop(leadingOffset: 5,  // leading, bottom, trailing\n                         bottomOffset: -5)\nitem1 \u003e\u003e item2.center                       // centerX, centerY\nitem1 \u003e\u003e item2.all                          // all edges\nitem1 \u003e\u003e item2                              // shorthand for .all\n```\n\nAvailable position target combinations are:\n\n* `all`\n* `allButTop`\n* `allButLeading`\n* `allButLeft`\n* `allButBottom`\n* `allButTrailing`\n* `allButRight`\n* `center`\n\nAll of them take offsets as arguments for exactly the constrained positions, in counter-clockwise order.\n\n## Constrain a Dimension\n\nYou constrain width and height just like positions:\n\n```swift\nitem1.width \u003e\u003e item2.height\n```\n\nAs with positions, you can omit redundant attributes, modify the target, and chain modifications:\n\n```swift\nitem1 \u003e\u003e item2.height.at(0.6).min  // \u003e= 60% of item2.height\n```\n\nYou can constrain a dimension to a constant size. These are equivalent:\n\n```swift\nitem.width \u003e\u003e .size(100)\nitem.width \u003e\u003e 100\n```\n\nOmit the dimension to constrain both dimensions to the same constant. These are equivalent:\n\n```swift\nitem \u003e\u003e .size(100)  // square with edge length 100\nitem \u003e\u003e 100         // same\n```\n\nYou can modify the constant size target like any other target, for one or both dimensions. And there are shorthand notations for minimum and maximum constants. These are equivalent:\n\n```swift\nitem \u003e\u003e .size(100).max  // width, height \u003c= 100\nitem \u003e\u003e .max(100)       // same\n```\n\n## Constrain Both Dimensions\n\nThe `size` target combines width and height. It works fully equivalent to those single dimensions:\n\n```swift\nitem1 \u003e\u003e item2.size.min  // at least as big as item2\n```\n\nA size target can also represent a constant size. These are equivalent:\n\n```swift\nitem \u003e\u003e .size(100, 50)  // size target with constants\nitem \u003e\u003e (100, 50)       // same\n```\n\nAnd there are also shorthand notations for minimum and maximum size. These are equivalent:\n\n```swift\nitem \u003e\u003e .size(100, 50).min  // at least 100 by 50\nitem \u003e\u003e .min(100, 50)       // same\n```\n\n## Constrain to the Parent\n\nNormally, in well structured code, views add and layout their own subviews. In those contexts, the parent (superview) of the constrained subviews is `self`, which makes it easy to constrain those subviews to any of their parent's attributes:\n\n```swift\nclass MySuperview: UIView {\n    // ... other code, including call to addSubviews() ...\n  \n    func addSubviews() {\n        let subview = addForAutoLayout(UIView())\n        subview \u003e\u003e allButBottom                   // constrain 3 edges to parent (self)\n        subview \u003e\u003e height.at(0.2)                 // constrain height to 20% of parent (self)\n    }\n}\n```\n\nSometimes, not all superviews are implemented as their own custom view class. In other words, some custom view- or controller classes add and layout whole subview hierarchies. In those contexts, the enclosing custom view or view controller controls the parent-child relation of its subviews and can directly constrain subviews to their parents:\n\n```swift\nclass MySuperview: UIView {\n    // ... other code, including call to addSubviews() ...\n\n    func addSubviews() {\n        let subview = addForAutoLayout(UIView())\n        let subsubview = subview.addForAutoLayout(UIView())\n        subsubview \u003e\u003e subview.allButBottom        // constrain 3 edges to parent\n        subsubview \u003e\u003e subview.height.at(0.2)      // constrain height to 20% of parent\n    }\n}\n```\n\nIf you still want to explicitly constrain a layout item to its parent, you can use the `parent` property. On a view, `parent` is its `superView`. On a layout guide, `parent` is its `owningView`. Of course, `parent` is optional, but all layout item based constrain targets can just be optional:\n\n```swift\nitem \u003e\u003e item.parent?.top.offset(10)          // constrain top to parent, inset 10\nitem \u003e\u003e item.parent?.allButBottom            // constrain 3 edges to parent\nitem \u003e\u003e item.parent?.size.at(0.3)            // constrain width and height to 30% of parent\nitem \u003e\u003e item.parent?.all(leadingOffset: 10)  // constrain all edges to parent, leading inset 10\nitem \u003e\u003e item.parent                          // constrain all edges to parent\n```\n\n## Constrain to the Safe Area on iOS\n\nOn iOS 11 and above, you can access the safe area of a view via the `safeArea` property and the parent's safe area via the optional `parentSafeArea` property.\n\nNormally, in well structured code where views add and layout their own subviews, you would simply call `safeArea` on `self`:\n\n```swift\nclass MyView: UIView {\n    // ... other code, including call to addSubviews() ...\n\n    func addSubviews() {\n        addForAutoLayout(MyContentView()) \u003e\u003e safeArea  // constrain content to safe area\n    }\n}\n```\n\nIf you find youself constraining many subviews to the safe area, there should probably be a content view containing them.\n\n## System Spacing on iOS and tvOS\n\nWith Apple's `NSLayoutAnchor`, you can make use of a mysterious \"system spacing\". Apple does not disclose how that is calculated and does not offer any concrete values you could access. Using system spacings through the `NSLayoutAnchor` API is a bit awkward, limited in how it is applied and limited in what it can be applied to.\n\nGetLaid exposes the system spacing as two global `CGFLoat` constants. It calls the actual Apple API to calculate the constants the first time you access them:\n\n1. `systemSiblingSpacing` is the gap the user's system wants between sibling views.\n2. `systemParentSpacing` is the inset the user's system wants from a view's edge to a contained subview.\n\nIt seems that on iOS both these system spacings are always the same. At least, I checked that from iPhone SE up to the newest 13\" iPad Pro, and from iOS 12.0 to iOS 13.3. So GetLaid also offers a universal `systemSpacing` which just returns `systemSiblingSpacing`.\n\nThe system spacing as a constant offers loads of flexibility:\n\n```swift\nitem2.left \u003e\u003e item1.right.offset(systemSpacing)  // gap between views\nitem \u003e\u003e item.parent?.top.offset(systemSpacing)   // inset to parent\nspacer.width \u003e\u003e .min(systemSpacing)              // minimum spacer width\n```\n\nRemember that these constants are not hardcoded but dynamically calculated on the actual user device, so they are absolutely true to what Apple intents for sibling gaps and parent insets, on any system and on any iOS/tvOS version. But also note that these two values do not capture the system spacing magic that `NSLayoutAnchor` offers in conjunction with baselines and font sizes and possibly in other contexts.\n\n[badge-pms]: https://img.shields.io/badge/supports-SPM%20%7C%20CocoaPods-green.svg?style=flat-square\n\n[badge-languages]: https://img.shields.io/badge/language-Swift-orange.svg?style=flat-square\n\n[badge-platforms]: https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS-lightgrey.svg?style=flat-square\n\n[badge-mit]: https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeface-io%2Fgetlaid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodeface-io%2Fgetlaid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeface-io%2Fgetlaid/lists"}