{"id":1313,"url":"https://github.com/FluidGroup/MondrianLayout","last_synced_at":"2025-08-02T04:31:03.348Z","repository":{"id":38077313,"uuid":"376525065","full_name":"FluidGroup/MondrianLayout","owner":"FluidGroup","description":"🏗 A way to build AutoLayout rapidly than using InterfaceBuilder(XIB, Storyboard) in iOS.","archived":false,"fork":false,"pushed_at":"2022-09-02T03:49:36.000Z","size":20685,"stargazers_count":171,"open_issues_count":11,"forks_count":11,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-07-07T17:49:39.063Z","etag":null,"topics":["autolayout","dsl","layout","resultbuilder","swiftui","uikit"],"latest_commit_sha":null,"homepage":"https://medium.com/geekculture/describing-autolayout-with-imaginable-how-it-lays-out-programmatically-mondrianlayout-71efe82f3149","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/FluidGroup.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":"2021-06-13T11:32:32.000Z","updated_at":"2025-06-22T07:16:36.000Z","dependencies_parsed_at":"2022-08-18T13:01:46.666Z","dependency_job_id":null,"html_url":"https://github.com/FluidGroup/MondrianLayout","commit_stats":null,"previous_names":["muukii/mondrianlayout"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/FluidGroup/MondrianLayout","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FluidGroup%2FMondrianLayout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FluidGroup%2FMondrianLayout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FluidGroup%2FMondrianLayout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FluidGroup%2FMondrianLayout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FluidGroup","download_url":"https://codeload.github.com/FluidGroup/MondrianLayout/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FluidGroup%2FMondrianLayout/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268334610,"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":["autolayout","dsl","layout","resultbuilder","swiftui","uikit"],"created_at":"2024-01-05T20:15:43.597Z","updated_at":"2025-08-02T04:31:01.846Z","avatar_url":"https://github.com/FluidGroup.png","language":"Swift","funding_links":["https://www.buymeacoffee.com/muukii"],"categories":["Layout"],"sub_categories":["Other Hardware"],"readme":"# MondrianLayout\n## A way to build AutoLayout rapidly than using InterfaceBuilder(XIB, Storyboard) in iOS.\n\n**Describing the layout ergonomically in the code**\n\n🏗 **Structured Layout API**\n\n```swift\nMondrian.buildSubviews(on: view) {\n  VStackBlock {\n  \n    titleLabel\n    \n    HStackBlock {\n      cancelButton\n      sendButton\n    }      \n  }\n  .padding(24)\n}\n```\n\n---\n\n💃 **To preserve flexibility of AutoLayout, we have classical style API**\n\n**Classical Layout API**\n```swift\nsendButton.mondrian.layout\n  .width(120)\n  .top(.toSuperview)\n  .trailing(.toSuperview)\n  .leading(.to(cancelButton).trailing)\n  .activate()\n```\n\n---\n\n## Support the project\n\u003ca href=\"https://www.buymeacoffee.com/muukii\"\u003e\n\u003cimg width=\"545\" alt=\"yellow-button\" src=\"https://user-images.githubusercontent.com/1888355/146226808-eb2e9ee0-c6bd-44a2-a330-3bbc8a6244cf.png\"\u003e\n\u003c/a\u003e\n\n\u003e 🤵🏻‍♂️💭\n\u003e We guess we still don't cover the all of use-cases.\n\u003e Please feel free to ask what you've faced case in Issues!\n\n\u003cimg width=\"246\" alt=\"CleanShot 2021-06-17 at 21 12 03@2x\" src=\"https://user-images.githubusercontent.com/1888355/122394225-b1da4e80-cfb0-11eb-9e62-f5627c817b66.png\"\u003e\n\nThis image laid out by MondrianLayout\n\n\u003cdetails\u003e\u003csummary\u003eLayout code\u003c/summary\u003e\n\u003cp\u003e\n\n```swift\nHStackBlock(spacing: 2, alignment: .fill) {\n  VStackBlock(spacing: 2, alignment: .fill) {\n    UIView.mock(\n      backgroundColor: .mondrianRed,\n      preferredSize: .init(width: 28, height: 28)\n    )\n\n    UIView.mock(\n      backgroundColor: .layeringColor,\n      preferredSize: .init(width: 28, height: 50)\n    )\n\n    UIView.mock(\n      backgroundColor: .mondrianYellow,\n      preferredSize: .init(width: 28, height: 28)\n    )\n\n    UIView.mock(\n      backgroundColor: .layeringColor,\n      preferredSize: .init(width: 28, height: 28)\n    )\n\n    HStackBlock(alignment: .fill) {\n      UIView.mock(\n        backgroundColor: .layeringColor,\n        preferredSize: .init(width: 28, height: 28)\n      )\n      UIView.mock(\n        backgroundColor: .layeringColor,\n        preferredSize: .init(width: 28, height: 28)\n      )\n    }\n  }\n\n  VStackBlock(spacing: 2, alignment: .fill) {\n    HStackBlock(spacing: 2, alignment: .fill) {\n      UIView.mock(\n        backgroundColor: .layeringColor,\n        preferredSize: .init(width: 28, height: 28)\n      )\n      VStackBlock(spacing: 2, alignment: .fill) {\n        HStackBlock(spacing: 2, alignment: .fill) {\n          UIView.mock(\n            backgroundColor: .mondrianYellow,\n            preferredSize: .init(width: 28, height: 28)\n          )\n          UIView.mock(\n            backgroundColor: .layeringColor,\n            preferredSize: .init(width: 28, height: 28)\n          )\n        }\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n      }\n    }\n\n    HStackBlock(spacing: 2, alignment: .fill) {\n      VStackBlock(spacing: 2, alignment: .fill) {\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n        UIView.mock(\n          backgroundColor: .mondrianBlue,\n          preferredSize: .init(width: 28, height: 28)\n        )\n      }\n\n      UIView.mock(\n        backgroundColor: .layeringColor,\n        preferredSize: .init(width: 28, height: 28)\n      )\n\n      VStackBlock(spacing: 2, alignment: .fill) {\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n      }\n    }\n\n    HStackBlock(spacing: 2, alignment: .fill) {\n      UIView.mock(\n        backgroundColor: .mondrianRed,\n        preferredSize: .init(width: 28, height: 28)\n      )\n      VStackBlock(spacing: 2, alignment: .fill) {\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n        UIView.mock(\n          backgroundColor: .layeringColor,\n          preferredSize: .init(width: 28, height: 28)\n        )\n      }\n    }\n\n  }\n\n}\n.overlay(\n  UILabel.mockMultiline(text: \"Mondrian Layout\", textColor: .white)\n    .viewBlock\n    .padding(4)\n    .background(\n      UIView.mock(\n        backgroundColor: .layeringColor\n      )\n      .viewBlock\n    )\n    .relative(bottom: 8, right: 8)\n)\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Structured layout API and Classical layout API\n\n- 🌟 Ergonomically Enables us to describe layout by DSL (like SwiftUI's layout).\n- 🌟 Automatic adding subviews according to layout representation.\n- 🌟 Supports integeration with system AutoLayout API.\n- 🌟 Provides classical layout API that describing constraints each view.\n\n## Introduction\n\nA DSL based layout builder with AutoLayout\n\n\nAutoLayout is super powerful to describe the layout and how it changes according to the bounding box.  \nWhat if we get a more ergonomic interface to declare the constraints.\n\n### Structured layout API\n\n| | |\n|---|---|\n|\u003cimg width=\"364\" src=\"https://user-images.githubusercontent.com/1888355/122650919-75555100-d170-11eb-8edf-834497dec211.png\" /\u003e|\u003cimg width=\"364\" alt=\"CleanShot 2021-06-17 at 21 13 11@2x\" src=\"https://user-images.githubusercontent.com/1888355/122394356-d9311b80-cfb0-11eb-8c8c-f5117593ffbe.png\"\u003e|\n\n\u003cimg width=\"649\" alt=\"CleanShot 2021-06-17 at 21 14 10@2x\" src=\"https://user-images.githubusercontent.com/1888355/122394462-f9f97100-cfb0-11eb-9838-91f22c148bd9.png\"\u003e\n\n### Unstructured layout API (classic style)\n\n\u003cimg width=\"330\" alt=\"classic@2x\" src=\"https://user-images.githubusercontent.com/1888355/124358722-84ea9480-dc5c-11eb-95f7-e0250779b65c.png\"\u003e\n\n## Future direction\n\n- Optimize the code - still verbose implementation because we're not sure if the API can be stable.\n- Brushing up the DSL - to be stable in describing.\n- Adding more modifiers for fine tuning in layout.\n- Tuning up the stack block's behavior.\n- Adding a way to setting constraints independently besides DSL\n  - AutoLayout is definitely powerful to describe the layout. We might need to set the constraints additionally since DSL can't describe every pattern of the layout.\n\n## Demo app\n\nYou can see many layout examples from the demo application.\n\n\nhttps://user-images.githubusercontent.com/1888355/122651186-142e7d00-d172-11eb-8bde-f4432d0a0ac9.mp4\n\n\n## Overview\n\nMondrianLayout enables us to describe layouts of subviews by DSL (powered by `resultBuilders`)  \nIt's like describing in SwiftUI, but this behavior differs a bit since laying out by AutoLayout system.\n\nTo describe layout, use `buildSubviews` as entrypoint.  \nThis method creates a set of NSLayoutConstraint, UILayoutGuide, and modifiers of UIView.  \nFinally, those apply. You don't need to call `addSubview`. that goes automatically according to hierarchy from layout descriptions.\n\n```swift\nclass MyView: UIView {\n\n  let nameLabel: UILabel\n  let detailLabel: UILabel\n\n  init() {\n    super.init(frame: .zero)\n    \n    // Seting up constraints constraints, layoutGuides and adding subviews\n    Mondrian.buildSubviews(on: self) {\n      VStackBlock {\n        nameLabel\n        detailLabel\n      }\n    }\n    \n    // Seting up constraints for the view itself.\n    Mondrian.layout {\n      self.mondrian.layout.width(200) // can be method cain.\n    }\n    \n  }\n}\n```\n\n### Examples\n\nSample code assumes run in `UIView`. (self is `UIView`)  \nYou can replace it with `UIViewController.view`.\n\n#### Layout subviews inside safe-area\n\nAttaching to top and bottom safe-area.\n\n```swift\nMondrian.buildSubviews(on: self) {\n  LayoutContainer(attachedSafeAreaEdges: .vertical) {\n    VStackBlock {\n      ...\n    }\n  }\n}\n```\n\nor\n\n```swift\nMondrian.buildSubviews(on: self) {\n  VStackBlock {\n    ...\n  }\n  .container(respectingSafeAreaEdges: .vertical)\n}\n```\n\n#### Put a view snapping to edge\n\n```swift\nMondrian.buildSubviews(on: self) {\n  ZStackBlock {\n    backgroundView.viewBlock.relative(0)    \n  }\n}\n```\n\nsynonyms:\n\n```swift\nZStackBlock(alignment: .attach(.all)) {\n  backgroundView\n}\n```\n\n```swift\nZStackBlock {\n  backgroundView.viewBlock.alignSelf(.attach(.all))\n}\n```\n\n#### Add constraints to view itself - using classic layout\n\n```swift\nMondrian.layout {\n  self.mondrian.layout.width(...).height(...)\n}\n```\n\nor\n```swift\nself.mondrian.layout.width(...).height(...).activate()\n```\n\n#### Stacking views on Z axis\n\n`relative(0)` fills to the edges of `ZStackBlock`.\n\n```swift\nMondrian.buildSubviews(on: self) {\n  ZStackBlock {\n    profileImageView.viewBlock.relative(0)\n    textOverlayView.viewBlock.relative(0)\n  }\n}\n```\n\n#### Centering a label with minimum padding\n\n```swift\nZStackBlock {\n  myLabel\n    .relative(.all, .min(20))\n}\n```\n\n```swift\nZStackBlock {\n  ZStackBlock {\n    myLabel\n  }\n  .padding(20) /// a minimum padding for the label in the container\n}\n```\n\n## Detail\n\n### Vertically and Horizontally Stack layout\n\n#### VStackBlock\n\nAlignment \n| center(default) | leading | trailing | fill |\n|---|---|---|---|\n|\u003cimg width=\"155\" alt=\"CleanShot 2021-06-17 at 00 06 10@2x\" src=\"https://user-images.githubusercontent.com/1888355/122244438-d75b4f80-ceff-11eb-90ea-8982758ed0b0.png\"\u003e|\u003cimg width=\"151\" alt=\"CleanShot 2021-06-17 at 00 05 19@2x\" src=\"https://user-images.githubusercontent.com/1888355/122244276-b7c42700-ceff-11eb-90d0-492c3fbc5076.png\"\u003e|\u003cimg width=\"159\" alt=\"CleanShot 2021-06-17 at 00 05 33@2x\" src=\"https://user-images.githubusercontent.com/1888355/122244312-c01c6200-ceff-11eb-888d-0a37b63f666a.png\"\u003e|\u003cimg width=\"159\" alt=\"CleanShot 2021-06-17 at 00 05 42@2x\" src=\"https://user-images.githubusercontent.com/1888355/122244341-c6124300-ceff-11eb-9da8-dcbb4425909a.png\"\u003e|\n\n#### HStackBlock\n\n| center(default) | top | bottom | fill |\n|---|---|---|---|\n|\u003cimg width=\"358\" alt=\"CleanShot 2021-06-17 at 00 09 43@2x\" src=\"https://user-images.githubusercontent.com/1888355/122245037-5486c480-cf00-11eb-872a-e98cfce7262e.png\"\u003e|\u003cimg width=\"359\" alt=\"CleanShot 2021-06-17 at 00 09 51@2x\" src=\"https://user-images.githubusercontent.com/1888355/122245054-58b2e200-cf00-11eb-9691-607a75060f75.png\"\u003e|\u003cimg width=\"362\" alt=\"CleanShot 2021-06-17 at 00 09 59@2x\" src=\"https://user-images.githubusercontent.com/1888355/122245073-5d779600-cf00-11eb-856d-0e48712377d7.png\"\u003e|\u003cimg width=\"355\" alt=\"CleanShot 2021-06-17 at 00 10 06@2x\" src=\"https://user-images.githubusercontent.com/1888355/122245096-62d4e080-cf00-11eb-99f2-2969a3ccc350.png\"\u003e|\n\n```swift\nMondrian.buildSubviews(on: self) {\n  VStackBlock(spacing: 4, alignment: alignment) {\n    UILabel.mockMultiline(text: \"Hello\", textColor: .white)\n      .viewBlock\n      .padding(8)\n      .background(UIView.mock(backgroundColor: .mondrianYellow))\n    UILabel.mockMultiline(text: \"Mondrian\", textColor: .white)\n      .viewBlock\n      .padding(8)\n      .background(UIView.mock(backgroundColor: .mondrianRed))\n    UILabel.mockMultiline(text: \"Layout!\", textColor: .white)\n      .viewBlock\n      .padding(8)\n      .background(UIView.mock(backgroundColor: .mondrianBlue))\n  }\n}\n```\n\n#### StackingSpacer\n\nAdds a space in stacking layout block.\n\n#### Background modifier\n\n```swift\nlabel\n  .viewBlock // To enable view describes layout\n  .padding(8)\n  .background(backgroundView)\n```\n\n\u003cimg width=\"74\" alt=\"CleanShot 2021-06-17 at 00 14 52@2x\" src=\"https://user-images.githubusercontent.com/1888355/122245871-0f16c700-cf01-11eb-91bc-019693736801.png\"\u003e\n\n#### Overlay modifier\n\n```swift\nlabel\n  .viewBlock // To enable view describes layout\n  .padding(8)\n  .overlay(overlayView)\n```\n\n#### Relative modifier\n\n`.relative` modifier describes that the content attaches to specified edges with padding.  \nNot specified edges do not have constraints to the edge. so the sizing depends on intrinsic content size.\n\nYou might use this modifier to pin to edge as an overlay content.\n\n```swift\nZStackBlock {\n  VStackBlock {\n    ...\n  }\n  .relative(bottom: 8, right: 8)\n}\n```\n\n#### Padding modifier\n\n`.padding` modifier is similar with `.relative` but something different.  \nDifferent with that, Not specified edges pin to edge with 0 padding.\n\n```swift\nZStackBlock {\n  VStackBlock {\n    ...\n  }\n  .padding(.horizontal, 10) // other edges work with 0 padding.\n}\n```\n\n#### ZStackBlock\n\n| | |\n|---|---|\n|\u003cimg width=\"159\" alt=\"CleanShot 2021-06-25 at 04 50 27@2x\" src=\"https://user-images.githubusercontent.com/1888355/123323763-efbb1200-d570-11eb-81f7-acecbb92ca55.png\"\u003e|\u003cimg width=\"220\" alt=\"CleanShot 2021-06-25 at 04 51 37@2x\" src=\"https://user-images.githubusercontent.com/1888355/123323843-0b261d00-d571-11eb-9679-752625ad856d.png\"\u003e|\n\nStacking views in Z axis (aligns in center)\n\n```swift\nZStackBlock {\n  view1\n  view2\n  view3\n}\n```\n\nExpands to specified edges each view\n\n```swift\nZStackBlock(alignment: .attach(.all)) {\n  view1\n  view2\n  view3\n}\n```\n\nSpecifying alignment each view\n\n```swift\nZStackBlock {\n\n  view1.viewBlock.alignSelf(.attach(.all))\n\n  view2.viewBlock.alignSelf(.attach([.top, .bottom]))\n  \n  view3.viewBlock.alignSelf(.attach(.top))\n}\n```\n\n\n\n\n\n## Updating layout dynamically\n\n`LayoutManager` does support it.  \nIf we need to change the layout each some conditions such as depending traits, this object helps that.\n\n## Animations\n\n// TODO: \nhttps://github.com/muukii/MondrianLayout/issues/19\n\n## Classic Layout API\n\nStructured layout API(DSL) does not cover the all of use-cases.  \nSometimes we still need a way to describe constraints for a complicated layout.\n\nMondrianLayout provides it as well other AutoLayout libraries.\n\n**Activate constraints independently**\n\n```swift\nview.mondrian.layout\n  .width(10)\n  .top(.toSuperview)\n  .right(.toSuperview)\n  .leading(.toSuperview)\n  .activate() // activate constraints and returns `ConstraintGroup`\n```\n\nBatch layout**\n\n```swift\n// returns `ConstraintGroup`\nMondrian.layout {\n\n  box1.mondrian.layout\n    .top(.toSuperview)\n    .left(.toSuperview)\n    .right(.to(box2).left)\n    .bottom(.toSuperview)\n\n  box2.mondrian.layout\n    .top(.toSuperview.top, .exact(10))\n    .right(.toSuperview)\n    .bottom(.toSuperview)\n}\n```\n\n### Examples\n\n#### Attach in horizontally\n\n```swift\nview.layout.horizontal(.toSuperview, .exact(10))\n```\n\n#### Attach in vertically\n\n```swift\nview.layout.vertical(.toSuperview, .exact(10))\n```\n\n#### Edge attaches to other edge\n\n```swift\nview.layout.edge(.toSuperview)\n```\n\n```swift\nview.layout.edge(.to(myLayoutGuide))\n```\n\n## Installation\n\n**CocoaPods**\n\n```ruby\npod \"MondrianLayout\"\n```\n\n**SwiftPM**\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/muukii/MondrianLayout.git\", exact: \"\u003cVERSION\u003e\")\n]\n```\n\n## LICENSE\n\nMondrianLayout is released under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFluidGroup%2FMondrianLayout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFluidGroup%2FMondrianLayout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFluidGroup%2FMondrianLayout/lists"}