{"id":1294,"url":"https://github.com/onmyway133/EasyAnchor","last_synced_at":"2025-08-02T04:30:49.103Z","repository":{"id":56901970,"uuid":"91187200","full_name":"onmyway133/EasyAnchor","owner":"onmyway133","description":"⚓️ Declarative, extensible, powerful Auto Layout","archived":false,"fork":false,"pushed_at":"2023-04-12T06:21:30.000Z","size":3308,"stargazers_count":457,"open_issues_count":1,"forks_count":28,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-07-31T13:42:33.265Z","etag":null,"topics":["anchor","autolayout","constraints","ios","macos"],"latest_commit_sha":null,"homepage":"https://onmyway133.com/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/onmyway133.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null},"funding":{"github":"onmyway133","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2017-05-13T16:23:34.000Z","updated_at":"2025-07-18T07:03:39.000Z","dependencies_parsed_at":"2023-07-13T08:24:49.397Z","dependency_job_id":null,"html_url":"https://github.com/onmyway133/EasyAnchor","commit_stats":{"total_commits":150,"total_committers":1,"mean_commits":150.0,"dds":0.0,"last_synced_commit":"a526134ef24d4112ccc926cb22e702792e1d116b"},"previous_names":["onmyway133/anchors"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/onmyway133/EasyAnchor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FEasyAnchor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FEasyAnchor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FEasyAnchor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FEasyAnchor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/onmyway133","download_url":"https://codeload.github.com/onmyway133/EasyAnchor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FEasyAnchor/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268334606,"owners_count":24233793,"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-08-02T02:00:12.353Z","response_time":74,"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":["anchor","autolayout","constraints","ios","macos"],"created_at":"2024-01-05T20:15:43.152Z","updated_at":"2025-08-02T04:30:48.627Z","avatar_url":"https://github.com/onmyway133.png","language":"Swift","funding_links":["https://github.com/sponsors/onmyway133"],"categories":["Layout","macos"],"sub_categories":["Other Hardware"],"readme":"# EasyAnchor\n\n❤️ Support my apps https://indiegoodies.com/ ❤️ \n\n❤️❤️😇😍🤘❤️❤️\n\n[![Version](https://img.shields.io/cocoapods/v/EasyAnchor.svg?style=flat)](http://cocoadocs.org/docsets/EasyAnchor)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![License](https://img.shields.io/cocoapods/l/EasyAnchor.svg?style=flat)](http://cocoadocs.org/docsets/EasyAnchor)\n[![Platform](https://img.shields.io/cocoapods/p/EasyAnchor.svg?style=flat)](http://cocoadocs.org/docsets/EasyAnchor)\n![Swift](https://img.shields.io/badge/%20in-swift%205.0-orange.svg)\n\n![](Screenshots/Artboard.png)\n\n# Table of contents\n\n- [Story](#story)\n- [Features](#features)\n- [Basic with Anchor](#basic-with-anchor)\n- [Inference](#inference)\n- [Find existing constraints](#find-existing-constraints)\n- [Animation](#animation)\n- [Update constraints with Group](#update-constraints-with-group)\n- [Extensible with ConstraintProducer](#extensible-with-constraintproducer)\n- [Build quickly with Builder](#build-quickly-with-builder)\n- [Debug Auto Layout](#debug-auto-layout)\n- [Support multiple screen sizes](#support-multiple-screen-sizes)\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/basic.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/find.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/group.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/apply.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/paging.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/fixed_spacing.gif\" width=\"85\" height=\"85\" /\u003e\n\u003cimg src=\"Screenshots/dynamic_spacing.gif\" width=\"85\" height=\"85\" /\u003e\n\u003c/div\u003e\n\n## Story\n\nI like to build view in code, so Auto Layout is my definite choice. The syntax has improved over the years, but I always want to do it with minimum effort. More repetitive code makes you tend to do copy paste and produce more bugs.\n\nRead more [How to make Auto Layout more convenient in iOS](https://medium.com/flawless-app-stories/how-to-make-auto-layout-more-convenient-in-ios-df3b42fed37f)\n\n### Auto Layout APIs history\n\nHow new APIs were introduced over the years, so you know to set your deployment target\n\n- `NSLayoutConstraint` since iOS 6, macOS 10.7\n- `isActive` since iOS 8, macOS 10.10\n- `NSLayoutAnchor`, `UI|NSLayoutGuide` since iOS 9, macOS 10.11\n\n### Do you need another Auto Layout framework?\n\nAll the Auto Layout frameworks you see are just convenient ways to build `NSLayoutConstraint`, in fact these are what you normally need\n\n- Call `addSubview` so that view is in hierarchy\n- Set `translatesAutoresizingMaskIntoConstraints = false`\n- Set `isActive = true` to enable constraints\n\nMost of the time, `NSLayoutAnchor` is what you need. But if you need more, **EasyAnchor** can be your choice.\n\n### Examples\n\n#### Tetris\n\nWell, you can use Auto Layout to make `Tetris`. Auto Layout plays well with `affine transform` too. See [code](https://github.com/onmyway133/EasyAnchor/blob/master/Example/EasyAnchorDemo/EasyAnchorDemo/Sources/Content/TetrisView.swift)\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/tetris.gif\" width=\"200\" height=\"295\" /\u003e\n\u003cbr /\u003e\n\u003cbr /\u003e\n\u003cbr /\u003e\n\u003c/div\u003e\n\n```swift\nactivate(\n  lineBlock.anchor.left.bottom\n)\n\n// later\nactivate(\n  firstSquareBlock.anchor.left.equal.to(lineBlock.anchor.right),\n  firstSquareBlock.anchor.bottom\n)\n\n// later\nactivate(\n  secondSquareBlock.anchor.right.bottom\n)\n```\n\n#### Piano\n\nThis is how to make a piano using `apply` and `fixed spacing`. See [code](https://github.com/onmyway133/EasyAnchor/blob/master/Example/EasyAnchorDemo/EasyAnchorDemo/Sources/Content/PianoView.swift)\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/piano.png\" width=\"300\" height=\"150\" /\u003e\n\u003c/div\u003e\n\n```swift\nactivate(\n  c.anchor.left,\n  b.anchor.right,\n  c.anchor.top.bottom,\n  c.anchor.top.bottom.width.apply(to: [d, e, f, g, a, b]),\n  c.anchor.fixedSpacingHorizontally(togetherWith: [d, e, f, g, a, b], spacing: 0)\n)\n\nactivate(\n  cd.anchor.top,\n  cd.anchor.size.equal.to(c.anchor.size).multiplier(2/3),\n  cd.anchor.top.size.apply(to: [de, fg, ga, ab]),\n  cd.anchor.centerX.equal.to(c.anchor.right),\n  de.anchor.centerX.equal.to(d.anchor.right),\n  fg.anchor.centerX.equal.to(f.anchor.right),\n  ga.anchor.centerX.equal.to(g.anchor.right),\n  ab.anchor.centerX.equal.to(a.anchor.right)\n)\n```\n\n#### More\n\nMore example can be found in [Example](https://github.com/onmyway133/EasyAnchor/tree/master/Example/EasyAnchorDemo)\n\n## Features\n\n- [x] Fluent builder syntax\n- [x] Easy to customize with protocol based\n- [x] Support iOS, macOS\n- [x] Support `LayoutGuide`\n- [x] Update and reset constraints\n- [x] Find existing constraints\n- [ ] Debug constraints\n- [ ] Visualize constraints\n\n## Basic with Anchor\n\n#### Access Anchor\n\nPrefer composition over extension, this is how you access `anchor`. Support `View`, `LayoutGuide`, `LayoutSupport`\n\n```swift\nlet view = UIView()\nview.anchor.left.right\n\nlet guide = UILayoutGuide()\nguide.anchor.width.height\n\ntopLayoutGuide.anchor.bottom\nbottomLayoutGuide.anchor.top\n```\n\n#### Activate constraints\n\nUse `activate` which accepts variadic parameters. This is important, no matter how you create constraints, they don't have any effect until you `activate` it ❗❗❗❗❗\n\n```swift\nactivate(\n  a.anchor.top.left,\n  b.anchor.top.right,\n  c.anchor.bottom.left,\n  d.anchor.bottom.right\n)\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/basic.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n#### Attributes\nSupports all attributes you can think of\n\n```swift\nanchor.top.left.bottom.right\n  .leading.trailing\n  .topMargin.bottomMargin.leftMargin.rightMargin\n  .centerX.centerY\n  .centerXWithinMargins.centerXWithinMargins\n  .lastBaseline.firstBaseline\n  .width.height\n```\n\n#### Relation\n\n```swift\na.anchor.top.equal.to(b.bottom)\na.anchor.width.greaterThanOrEqual.to(b.anchor.height)\na.anchor.width.lessThanOrEqual.to(b.anchor)\n``` \n\n#### Configuration\nThis is how to apply `constant`, `multiplier`, `priority`, `identifier`\n\n```swift\na.anchor.top.equal.to(b.anchor.bottom)\n  .constant(10).multiplier(1.5).priority(999)\n  .id(\"verticalSpacingBetweenA-B\")\n```\n\n#### Reference\nGet references to constraints to modify it later on. In the `ref` closure, we get access to all the created constraints\n\n```swift\nvar constraint: NSLayoutConstraint?\nactivate(\n  view.anchor.center.constant(10).ref({ constraint = $0.first })\n)\n```\n\n#### Convenient attributes\n\nUse convenient attributes which combine multiple inner attributes\n\n```swift\na.anchor.center  // centerX, centerY\na.anchor.size  // width, height\na.anchor.edges  // top, right, bottom, left\n```\n\n#### Convenient methods\n\nInsets\n\n```swift\na.anchor.edges.insets(EdgeInsets(top: 1, left: 2, bottom: 3, right: 4))  // top+1, left+2, bottom+3, right+4\na.anchor.edges.insets(5)  // top+5, left+5, bottom-5, right-5\n```\n\nPadding\n\n```swift\na.anchor.paddingHorizontally(20) // leading+20, trailing-20\nb.anchor.paddingVertically(20)\t// top+20, bottom-20\n```\n\n#### Size\n\nSize to another anchor\n\n```swift\na.anchor.height.equal.to(b.anchor.width)\nc.anchor.size.equal.to(d.anchor)\n```\n\nSize to a constant\n\n```swift\na.anchor.height.equal.to(20)  // height==20\nb.anchor.size.equal.to(20)  // width==20, height==20\n```\n\nYou can't just use `constant` because **EasyAnchor** will infer to the superview\n\n```swift\nc.anchor.width.constant(20)  // width==superview.width+20\n```\n\n#### Ratio\n\n```swift\na.anchor.height.equal.to(a.anchor.width)  // height==width\n```\n\nAlternatively, you can just use `ratio`\n\n```swift\na.anchor.width.constant(10)\na.anchor.height.ratio(2) // height==width*2\n```\n\n```swift\na.anchor.height.constant(10)\na.anchor.width.ratio(2) // width==height*2\n```\n\n## Inference\nYou know what you mostly want to do. So does **EasyAnchor** 🎉. It does its best to infer so don't have to write \"obvious\" code\n\n#### Most of the time, you want to constraint to the `superview`\n\n```swift\na.anchor.top.left // a.top == a.superview.top, a.left == a.superview.left\n```\n\n#### Most of the time, you want to constraint to the same attributes\n\n```swift\na.anchor.top.bottom.width.equal.to(b.anchor) // a.top == b.top, a.bottom == b.bottom, a.width == b.width\n```\n\n\n## Find existing constraints\n\nYou don't need to declare variables to store `constraints`, you can just retrieve them back\n\n```swift\na.anchor.find(.height)?.constant = 100\n\n// later\nb.anchor.find(.height)?.constant = 100\n\n// later\nc.anchor.find(.height)?.constant = 100\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/find.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n## Animation\n\nAnimation is simple. You just change your `constraint` 's `isActive` or `constant` properties, then `layoutIfNeeded` in an animation block. You can use `UIView.animate` or `UIViewPropertyAnimator`\n\n```swift\n// Change constraint\na.anchor.find(.height)?.constant = 100\nloginButtonHeightConstraint.isActive = false\n\nlet animator = UIViewPropertyAnimator(duration: 1, dampingRatio: 0.7)\nanimator.addAnimations { [weak self] in\n  self?.view.layoutIfNeeded()\n}\n\nanimator.startAnimation(afterDelay: 1)\n```\n\n## Update constraints with Group\n\n`activate` is just a convenient way to produce `group`, then set `isActive` on the `Group`. If you just want to group a set of constraints, then set `isActive` later on, use function `group`\n\nIn this example, we have 4 groups, then take turn to toggle which group gets activated\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/group.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n```swift\nfunc toggle(_ group: Group) {\n  [g1, g2, g3, g4].forEach { g in\n    guard let g = g else {\n      return\n    }\n\n    g.isActive = (g == group)\n  }\n}\n\ng1 = group(a.anchor.top.left)\ng2 = group(a.anchor.top.right)\ng3 = group(a.anchor.bottom.right)\ng4 = group(a.anchor.bottom.left)\n\ng1.isActive = true\n\nanimator = Animator(view: self, animations: [\n  {\n    self.toggle(self.g2)\n  },\n  {\n    self.toggle(self.g3)\n  },\n  {\n    self.toggle(self.g4)\n  },\n  {\n    self.toggle(self.g1)\n  }\n  ])\n\nanimator.start()\n```\n\n## Extensible with ConstraintProducer\n\n`Group` is a set of `NSLayoutConstraint`, which are produced by `ConstraintProducer`\n\n```swift\npublic protocol ConstraintProducer {\n  func constraints() -\u003e [NSLayoutConstraint]\n}\n```\n\nFor now, there is `Anchor` and `Builder` which conforms to `ConstraintProducer`, you can extend **EasyAnchor** easily by conform to `ConstraintProducer`. For example\n\n```swift\n// This accepts a list of views, and build constraints\nclass MyAwesomeLayout: ConstraintProducer {\n  init(views: [UIView]) {\n    // Your code goes here\n  }\n\n  func constraints() -\u003e [NSLayoutConstraint] {\n    // Your code goes here\n    return []\n  }\n}\n\nlet awesomeLayout = MyAwesomeLayout(views: [view1, view2])\nactivate(awesomeLayout)\n```\n\n## Build quickly with Builder\n\nWell, `Anchor` is for making `constraints` between 2 views. If you want to make `constraints` for multiple views at once, you can use multiple `Anchor`. There are some tasks that you do often, let `Builder` help you. These method below use `Builder` under the hood\n\n**EasyAnchor** has a set of builders to help you avoid repetitive tasks and build UIs quickly 😎\n\n#### Apply\n\nApply the same anchor to other views\n\n```swift\na.anchor.left.height.apply(to: [b, c]),\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/apply.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n#### Paging\nBuild a paging scrollView horizontally\n\n```swift\naddSubview(scrollView)\n[a, b, c, d].forEach {\n  scrollView.addSubview($0)\n}\n\nactivate(\n  scrollView.anchor.edges.insets(8),\n  a.anchor.pagingHorizontally(togetherWith: [b, c, d], in: scrollView)\n)\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/paging.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n#### Fixed spacing\nAdd fixed spacing. The views will resize\n\n```swift\nactivate(\n  container.anchor.edges.insets(8),\n  a.anchor.left.top.bottom,\n  c.anchor.right,\n  a.anchor.top.bottom.width.apply(to: [b, c]),\n  a.anchor.fixedSpacingHorizontally(togetherWith: [b, c], spacing: 50)\n)\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/fixed_spacing.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n#### Dynamic spacing\nAdd dynamic spacing using LayoutGuide. The spacing will resize\n\n```swift\nactivate(\n  container.anchor.edges.insets(8),\n  a.anchor.size.equal.to(30),\n  b.anchor.size.equal.to(30),\n  c.anchor.size.equal.to(30),\n  a.anchor.left.centerY,\n  a.anchor.centerY.apply(to: [b, c]),\n  c.anchor.right,\n  a.anchor.dynamicSpacingHorizontally(togetherWith: [b, c])\n)\n```\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/dynamic_spacing.gif\" width=\"200\" height=\"200\" /\u003e\n\u003c/div\u003e\n\n## Debug Auto Layout\n\n- Read [Debugging Auto Layout](https://github.com/onmyway133/blog/issues/23)\n\n## Support multiple screen sizes \n\n- Use `Group` to declare many sets of constraints for different screen sizes / size classes\n- Use ratio, read [Auto Layout with different screen sizes](https://github.com/onmyway133/blog/issues/35)\n\n## Installation\n\n**EasyAnchor** is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'EasyAnchor'\n```\n\n**EasyAnchor** is also available through [Carthage](https://github.com/Carthage/Carthage).\nTo install just write into your Cartfile:\n\n```ruby\ngithub \"onmyway133/EasyAnchor\"\n```\n\n**EasyAnchor** can also be installed manually. Just download and drop `Sources` folders in your project.\n\n## Author\n\nKhoa Pham, onmyway133@gmail.com\n\n## Contributing\n\nWe would love you to contribute to **EasyAnchor**, check the [CONTRIBUTING](https://github.com/onmyway133/EasyAnchor/blob/master/CONTRIBUTING.md) file for more info.\n\n## License\n\n**EasyAnchor** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/EasyAnchor/blob/master/LICENSE.md) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonmyway133%2FEasyAnchor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fonmyway133%2FEasyAnchor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonmyway133%2FEasyAnchor/lists"}