{"id":13525670,"url":"https://github.com/psharanda/FixFlex","last_synced_at":"2025-04-01T05:32:05.318Z","repository":{"id":216167595,"uuid":"740208236","full_name":"psharanda/FixFlex","owner":"psharanda","description":"Declarative Auto Layout code that is easy to write, read, and modify","archived":false,"fork":false,"pushed_at":"2024-07-11T20:31:29.000Z","size":5137,"stargazers_count":34,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-21T12:02:08.101Z","etag":null,"topics":["auto-layout","autolayout","ios","layout","macos","nslayoutanchor","nslayoutconstraint","nslayoutconstraints","nsview","swift","ui","uikit","uilayoutguide","uiview","vfl","visual-format-language"],"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/psharanda.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-07T20:40:23.000Z","updated_at":"2025-02-07T12:36:24.000Z","dependencies_parsed_at":"2024-06-24T21:59:08.157Z","dependency_job_id":"486ba2f0-1bf7-4c8c-a631-230665b02dbf","html_url":"https://github.com/psharanda/FixFlex","commit_stats":{"total_commits":7,"total_committers":1,"mean_commits":7.0,"dds":0.0,"last_synced_commit":"7441c7947733cc453707ca421f47831db3798df1"},"previous_names":["psharanda/fixflex"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psharanda%2FFixFlex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psharanda%2FFixFlex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psharanda%2FFixFlex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psharanda%2FFixFlex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/psharanda","download_url":"https://codeload.github.com/psharanda/FixFlex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246591510,"owners_count":20801983,"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":["auto-layout","autolayout","ios","layout","macos","nslayoutanchor","nslayoutconstraint","nslayoutconstraints","nsview","swift","ui","uikit","uilayoutguide","uiview","vfl","visual-format-language"],"created_at":"2024-08-01T06:01:20.934Z","updated_at":"2025-04-01T05:32:02.503Z","avatar_url":"https://github.com/psharanda.png","language":"Swift","funding_links":[],"categories":["Libs"],"sub_categories":["Layout"],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"Readme/static/logo.png\" width=\"50%\" alt=\"FixFlex Logo\" /\u003e\n\u003c/p\u003e\n      \n`FixFlex` is a simple yet powerful Auto Layout library built on top of the NSLayoutAnchor API, a swifty and type-safe reimagination of [Visual Format Language](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html)\n\n## Features\n\n- Declarative Auto Layout code that is easy to write, read, and modify\n- Simple API with 2 functions and 4 specifiers, covering 99% of layout use cases\n- Implementation is only 300 lines of code\n- Compatible with any other Auto Layout code\n- Basically generates a bunch of activated `NSLayoutConstraint` and `UILayoutGuide`\n- Keeps your view hierarchy flat, no need for exta containers\n- Lightweight alternative to `UIStackView`\n- Super straightforward mental model\n- Typesafe alternative to VFL\n- Dynamic Type and Right-To-Left friendly\n- Automatically sets `translatesAutoresizingMaskIntoConstraints` to false\n- Supports iOS 12.0+ / Mac OS X 10.13+ / tvOS 12.0+\n\n## Usage\n\nImagine we want to create a layout like this:\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_CellWithIconTitleSubtitleAndChevron__default@3x.png\"\n     width=\"200\"/\u003e\n\n1. Let's scan the layout horizontally and translate it into FixFlex code:\n\n\u003cimg class=\"snapshot\"\n     src=\"Readme/static/hstack.png\"\n     width=\"400\"/\u003e\n\nMost of the views and spacings have a fixed width (`Fix`), while the title and subtitle widths are flexible, designed to occupy the remaining space (`Flex`):\n\n```swift\nparent.fx.hstack(Fix(15),\n                 Fix(iconView, 44),\n                 Fix(15),\n                 Flex([titleLabel, subtitleLabel]),\n                 Fix(15),\n                 Fix(chevron, 20),\n                 Fix(15))\n```\n\n2. Vertically, we have three distinct groups of views. Starting with the icon:\n\n\u003cimg class=\"snapshot\"\n     src=\"Readme/static/vstack1.png\"\n     width=\"400\"/\u003e\n\nWe do a spacing at the top using `Fix`. The bottom spacing should be at least 15pt, for the case when the labels' height is less than the icon's height:\n\n```swift\nparent.fx.vstack(Fix(15),\n                 Fix(iconView, 44),\n                 Flex(min: 15))\n```\n\n3. Next, we perform a vertical scan of the title and subtitle:\n\n\u003cimg class=\"snapshot\"\n     src=\"Readme/static/vstack2.png\"\n     width=\"400\"/\u003e\n\n```swift\nparent.fx.vstack(Fix(15),\n                 Flex(titleLabel),\n                 Flex(subtitleLabel),\n                 Fix(15))\n```\n\n4. Finally, we scan the chevron vertically:\n\n\u003cimg class=\"snapshot\"\n     src=\"Readme/static/vstack3.png\"\n     width=\"400\"\n/\u003e\n\nTo center the chevron, we ensure the top spacing is equal to the bottom spacing using `Fill`:\n\n```swift\nparent.fx.vstack(Fill(),\n                 Fix(chevron, 30),\n                 Fill())\n```\n\nThat's it! The best part is how easy it is to modify FixFlex layout code, inserting extra padding or views effortlessly, without the need to rewire constraints.\n\n## API\n\n### hstack/vstack\n\n`FixFlex` provides two functions for laying out views horizontally (`hstack`) and vertically (`vstack`), accessible through the `view.fx.*` namespace.\n\nYou can specify `startAnchor`/`endAnchor` to layout items between arbitrary anchors instead of the view's edges. `startOffset`/`endOffset` are used to add spacing or offsets from the `startAnchor` and `endAnchor` respectively.\n\nBy default, `hstack` works in natural positioning mode and operates using `leadingAnchor`/`trailingAnchor`. This setup ensures that the layout is mirrored for Right-to-Left languages. However, this behavior can be overridden by enabling the `useAbsolutePositioning` flag. When this flag is set to true, `hstack` shifts to using `leftAnchor`/`rightAnchor` for layout positioning.\n\n```swift\nfunc hstack(\n        startAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use leadingAnchor or leftAnchor\n        startOffset: CGFloat = 0,\n        endAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use trailingAnchor or rightAnchor\n        endOffset: CGFloat = 0,\n        useAbsolutePositioning: Bool = false, // if true, we use leftAnchor/rightAnchor based positioning (force Left-To-Right)\n        _ intents: SizingIntent...\n    ) -\u003e StackingResult\n```\n\n```swift\nfunc vstack(\n        startAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use topAnchor\n        startOffset: CGFloat = 0,\n        endAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use bottomAnchor\n        endOffset: CGFloat = 0,\n        _ intents: SizingIntent...\n    ) -\u003e StackingResult\n```\n\nA `SizingIntent` is essentially an instruction for calculating the width or height of:\n\n- a spacer (for which a `UILayoutGuide` is created behind the scenes)\n- a view\n- an array of views (when they are aligned in parallel)\n\nConcrete instances of `SizingIntent` can be created using specialized builder functions:\n\n### Fix\n\nUsed for specifying the exact size of a view/spacer.\n\n```swift\nfunc Fix(_ value: CGFloat) -\u003e SizingIntent\n\nfunc Fix(_ view: _View, _ value: CGFloat) -\u003e SizingIntent\n\nfunc Fix(_ views: [_View], _ value: CGFloat) -\u003e SizingIntent\n```\n\n### Flex\n\nUseful for sizes that change dynamically. Optionally, it is possible to specify min/max constraints and in-place priority settings for hugging and compression resistance.\n\n```swift\nfunc Flex(min: CGFloat? = nil, max: CGFloat? = nil) -\u003e SizingIntent\n\nfunc Flex(_ view: _View, min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -\u003e SizingIntent\n\nfunc Flex(_ views: [_View], min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -\u003e SizingIntent\n```\n\n### Fill\n\n`Fill` allows a view/spacer to proportionally occupy the available free space based on its weight. It's particularly useful for achieving equal spacing, centering elements, or for designing symmetrical layouts like tables or grids.\n\n```swift\nfunc Fill(weight: CGFloat = 1.0) -\u003e SizingIntent\n\nfunc Fill(_ view: _View, weight: CGFloat = 1.0) -\u003e SizingIntent\n\nfunc Fill(_ views: [_View], weight: CGFloat = 1.0) -\u003e SizingIntent\n```\n\n### Match\n\nThis is used to match the size of a view or spacer to a specified `NSLayoutDimension`. It is particularly useful for aligning the sizes of different views or spacers, or for making their sizes proportional to each other.\n\n```swift\npublic func Match(dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -\u003e SizingIntent\n\npublic func Match(_ view: _View, dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -\u003e SizingIntent\n\npublic func Match(_ views: [_View], dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -\u003e SizingIntent\n```\n\n## How it works\n\nFixFlex is not a black box and doesn't use any magic. It is simply a declarative and convenient way to create constraints and layout guides. Let's take a look at how FixFlex is translated into standard Auto Layout calls when we want to center vertically two labels:\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_VerticallyCenterTwoLabels__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Flex([topLabel, bottomLabel]))\n\nparent.fx.vstack(Fill(),\n                 Flex(topLabel),\n                 Fix(5),\n                 Flex(bottomLabel),\n                 Fill())\n```\n\nUnder the hood, FixFlex creates constraints and layout guides which equivalent to the following:\n\n```swift\ntopLabel.translatesAutoresizingMaskIntoConstraints = false\nbottomLabel.translatesAutoresizingMaskIntoConstraints = false\n\nlet layoutGuideTop = UILayoutGuide()\nlet layoutGuideMiddle = UILayoutGuide()\nlet layoutGuideBottom = UILayoutGuide()\n\nparent.addLayoutGuide(layoutGuideTop)\nparent.addLayoutGuide(layoutGuideMiddle)\nparent.addLayoutGuide(layoutGuideBottom)\n\nNSLayoutConstraint.activate([\n    // hstack\n    topLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),\n    topLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),\n    bottomLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),\n    bottomLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),\n    //vstack\n    layoutGuideTop.topAnchor.constraint(equalTo: parent.topAnchor),\n    layoutGuideTop.bottomAnchor.constraint(equalTo: topLabel.topAnchor),\n    topLabel.bottomAnchor.constraint(equalTo: layoutGuideMiddle.topAnchor),\n    layoutGuideMiddle.heightAnchor.constraint(equalToConstant: 5),\n    layoutGuideMiddle.bottomAnchor.constraint(equalTo: bottomLabel.topAnchor),\n    bottomLabel.bottomAnchor.constraint(equalTo: layoutGuideBottom.topAnchor),\n    layoutGuideBottom.bottomAnchor.constraint(equalTo: parent.bottomAnchor),\n    layoutGuideTop.heightAnchor.constraint(equalTo: layoutGuideBottom.heightAnchor),\n])\n```\n\nHuh, that's a lot of code to write, and imagine needing to modify it — inserting an extra view or changing the order. Once you try FixFlex, you won't want to go back!\n\n## Examples\n\n\n\n### Fill Parent With Inset\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_FillParentWithInset__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Fix(15),\n                 Flex(child),\n                 Fix(15))\n\nparent.fx.vstack(Fix(15),\n                 Flex(child),\n                 Fix(15))\n```\n\n\n\n### Pin To Parent Trailing Bottom\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_PinToParentTrailingBottom__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Flex(),\n                 Fix(child, 100),\n                 Fix(15))\n\nparent.fx.vstack(Flex(),\n                 Fix(child, 50),\n                 Fix(15))\n```\n\n\n\n### Center In Parent\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_CenterInParent__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Fill(),\n                 Fix(child, 100),\n                 Fill())\n\nparent.fx.vstack(Fill(),\n                 Fix(child, 50),\n                 Fill())\n```\n\n\n\n### Center Label In Parent\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_CenterLabelInParent__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Fill(),\n                 Flex(label),\n                 Fill())\n\nparent.fx.vstack(Fill(),\n                 Flex(label),\n                 Fill())\n```\n\n\n\n### Vertically Center Two Labels\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_VerticallyCenterTwoLabels__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Flex([topLabel, bottomLabel]))\n\nparent.fx.vstack(Fill(),\n                 Flex(topLabel),\n                 Fix(5),\n                 Flex(bottomLabel),\n                 Fill())\n```\n\n\n\n### Cell With Icon Title Subtitle And Chevron\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_CellWithIconTitleSubtitleAndChevron__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Fix(15),\n                 Fix(iconView, 44),\n                 Fix(15),\n                 Flex([titleLabel, subtitleLabel]),\n                 Fix(15),\n                 Fix(chevron, 20),\n                 Fix(15))\n\nparent.fx.vstack(Fix(15),\n                 Fix(iconView, 44),\n                 Flex(min: 15))\n\nparent.fx.vstack(Fix(15),\n                 Flex(titleLabel),\n                 Flex(subtitleLabel),\n                 Fix(15))\n\nparent.fx.vstack(Fill(),\n                 Fix(chevron, 30),\n                 Fill())\n```\n\n\n\n### Card With Icon Title And Subtitle\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_CardWithIconTitleAndSubtitle__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.hstack(Fix(5),\n                 Flex([iconView, titleLabel, subtitleLabel]),\n                 Fix(5))\n\nparent.fx.vstack(Fix(5),\n                 Fix(iconView, 50),\n                 Fix(10),\n                 Flex(titleLabel),\n                 Flex(subtitleLabel),\n                 Fix(5))\n```\n\n\n\n### Labels Row With Not Enough Space For Both\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_LabelsRowWithNotEnoughSpaceForBoth__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Flex([leftLabel, rightLabel]))\n\nparent.fx.hstack(Flex(leftLabel, compressionResistancePriority: .required),\n                 Fix(5),\n                 Flex(rightLabel))\n```\n\n\n\n### Labels Split\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_LabelsSplit__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Fix(5),\n                 Flex([label1, label2, label3]),\n                 Fix(5))\n\nparent.fx.hstack(Fix(5),\n                 Fill(label1, weight: 2),\n                 Fix(5),\n                 Fill(label2),\n                 Fix(5),\n                 Fill(label3),\n                 Fix(5))\n```\n\n\n\n### Flex Min Max\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_FlexMinMax__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Fix(5),\n                 Flex(label1),\n                 Flex(label2),\n                 Flex(label3),\n                 Fix(5))\n\nparent.fx.hstack(Fix(5),\n                 Flex(label1),\n                 Flex(),\n                 Fix(5))\n\nparent.fx.hstack(Fix(5),\n                 Flex(label2, min: 175),\n                 Flex(),\n                 Fix(5))\n\nparent.fx.hstack(Fix(5),\n                 Flex(label3, max: 100),\n                 Flex(),\n                 Fix(5))\n```\n\n\n\n### Put Between Anchors\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_PutBetweenAnchors__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Flex([label, leadingView, trailingView]))\n\nparent.fx.hstack(Fill(),\n                 Flex(label),\n                 Fill())\n\nparent.fx.hstack(startAnchor: label.leadingAnchor,\n                 endAnchor: label.trailingAnchor,\n                 Fix(leadingView, 20),\n                 Flex(),\n                 Fix(trailingView, 20))\n```\n\n\n\n### Put Between Anchors Absolute\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_PutBetweenAnchorsAbsolute__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Flex([label, leadingView, trailingView]))\n\nparent.fx.hstack(Fill(),\n                 Flex(label),\n                 Fill())\n\nparent.fx.hstack(startAnchor: label.leftAnchor,\n                 endAnchor: label.rightAnchor,\n                 useAbsolutePositioning: true,\n                 Fix(leadingView, 20),\n                 Flex(),\n                 Fix(trailingView, 20))\n```\n\n\n\n### Shadow Using Match\n\n\u003cimg class=\"snapshot\"\n     src=\"FixFlexSamples/Ref/ReferenceImages_64/FixFlexSamplesTests.FixFlexTests/test_ShadowUsingMatch__default@3x.png\"\n     width=\"200\"/\u003e\n\n```swift\nparent.fx.vstack(Fill(),\n                 Flex(label),\n                 Fill())\n\nparent.fx.hstack(Fill(),\n                 Flex(label),\n                 Fill())\n\nparent.fx.vstack(startAnchor: label.topAnchor,\n                 Fix(10),\n                 Match(matchView, dimension: label.heightAnchor),\n                 Flex())\n\nparent.fx.hstack(startAnchor: label.leadingAnchor,\n                 Fix(10),\n                 Match(matchView, dimension: label.widthAnchor),\n                 Flex())\n```\n\n\n\n## Integration\n\n### [Swift Package Manager](https://github.com/apple/swift-package-manager)\n\nUse Swift Package Manager and add dependency to `Package.swift` file.\n\n```swift\n  dependencies: [\n    .package(url: \"https://github.com/psharanda/FixFlex.git\", .upToNextMajor(from: \"1.0.0\"))\n  ]\n```\n\nAlternatively, in Xcode select `File \u003e Add Package Dependencies…` and add FixFlex repository URL:\n\n```\nhttps://github.com/psharanda/FixFlex.git\n```\n\n### Carthage\n\nAdd `github \"psharanda/FixFlex\"` to your `Cartfile`\n\n### CocoaPods\n`FixFlex` is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod \"FixFlex\"\n```\n\n## Contributing\n\nWe welcome contributions! If you find a bug, have a feature request, or want to contribute code, please open an issue or submit a pull request.\n\n## License\n\nFixFlex is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsharanda%2FFixFlex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpsharanda%2FFixFlex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsharanda%2FFixFlex/lists"}