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
- Declarative Auto Layout code that is easy to write, read, and modify
- Simple API with 2 functions and 4 specifiers, covering 99% of layout use cases
- Single-file implementation with a tiny surface area
- Compatible with any other Auto Layout code
- Basically generates a bunch of activated
NSLayoutConstraintandUILayoutGuide - Keeps your view hierarchy flat, no need for extra containers
- Lightweight alternative to
UIStackView - Super straightforward mental model
- Typesafe alternative to VFL
- Dynamic Type and Right-To-Left friendly
- Automatically sets
translatesAutoresizingMaskIntoConstraintsto false - Fully documented with 100% test coverage
- Supports iOS 12.0+ / macOS 10.13+ / tvOS 12.0+
Imagine we want to create a layout like this:
- Let's scan the layout horizontally and translate it into FixFlex code:
Most 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):
parent.fx.hstack(Fix(15),
Fix(iconView, 44),
Fix(15),
Flex([titleLabel, subtitleLabel]),
Fix(15),
Fix(chevron, 20),
Fix(15))- Vertically, we have three distinct groups of views. Starting with the icon:
We 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:
parent.fx.vstack(Fix(15),
Fix(iconView, 44),
Flex(min: 15))- Next, we perform a vertical scan of the title and subtitle:
parent.fx.vstack(Fix(15),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(15))- Finally, we scan the chevron vertically:
To center the chevron, we ensure the top spacing is equal to the bottom spacing using Fill:
parent.fx.vstack(Fill(),
Fix(chevron, 30),
Fill())That'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.
FixFlex provides two functions for laying out views horizontally (hstack) and vertically (vstack), accessible through the view.fx.* namespace.
You 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. Pass nil to leave a side unpinned (useful for allowing overflow).
By 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.
func hstack(
startAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use leadingAnchor or leftAnchor
startOffset: CGFloat? = 0, // if nil, we do not pin to startAnchor
endAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use trailingAnchor or rightAnchor
endOffset: CGFloat? = 0, // if nil, we do not pin to endAnchor
useAbsolutePositioning: Bool = false, // if true, we use leftAnchor/rightAnchor based positioning (force Left-To-Right)
_ intents: SizingIntent...
) -> StackingResultfunc vstack(
startAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use topAnchor
startOffset: CGFloat? = 0, // if nil, we do not pin to startAnchor
endAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use bottomAnchor
endOffset: CGFloat? = 0, // if nil, we do not pin to endAnchor
_ intents: SizingIntent...
) -> StackingResultStackingResult gives access to everything created during stacking so you can adjust or deactivate pieces later:
constraints: all constraints activated for the stacklayoutGuides: guides created for spacers/fills
A SizingIntent is essentially an instruction for calculating the width or height of:
- a spacer (for which a
UILayoutGuideis created behind the scenes) - a view
- an array of views (when they are aligned in parallel)
Concrete instances of SizingIntent can be created using specialized builder functions:
Used for specifying the exact size of a view/spacer.
func Fix(_ value: CGFloat) -> SizingIntent
func Fix(_ view: _View, _ value: CGFloat) -> SizingIntent
func Fix(_ views: [_View], _ value: CGFloat) -> SizingIntentUseful for sizes that change dynamically. Optionally, it is possible to specify min/max constraints and in-place priority settings for hugging and compression resistance.
func Flex(min: CGFloat? = nil, max: CGFloat? = nil) -> SizingIntent
func Flex(_ view: _View, min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -> SizingIntent
func Flex(_ views: [_View], min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -> SizingIntentFill 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.
func Fill(weight: CGFloat = 1.0) -> SizingIntent
func Fill(_ view: _View, weight: CGFloat = 1.0) -> SizingIntent
func Fill(_ views: [_View], weight: CGFloat = 1.0) -> SizingIntentThis 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.
public func Match(dimension: NSLayoutDimension, multiplier: CGFloat = 1, offset: CGFloat = 0) -> SizingIntent
public func Match(_ view: _View, dimension: NSLayoutDimension, multiplier: CGFloat = 1, offset: CGFloat = 0) -> SizingIntent
public func Match(_ views: [_View], dimension: NSLayoutDimension, multiplier: CGFloat = 1, offset: CGFloat = 0) -> SizingIntentYou can customize generated pieces via callbacks on SizingIntent:
onCreateStartConstraint(_:)lets you tweak each constraint that pins an item to the previous anchor (or stack start)onCreateEndConstraint(_:)lets you tweak the constraint that pins the final item to the end anchoronCreateDimensionConstraint(_:)lets you tweak each size constraint (e.g., priority or identifier)onCreateLayoutGuide(_:)gives you the implicitUILayoutGuidefor configuration
Each SizingIntent can also specify a spacingBefore(_:) to add (or subtract, via negative values) the gap before that item—handy for overlaps or custom per-item spacing.
hstackdefaults to leading/trailing so layouts mirror in RTL; setuseAbsolutePositioning: trueto force left/right.startOffset/endOffset = nilleaves the leading/trailing item unpinned and should only be used when other constraints define its position.Flexon views keeps existing hugging/compression unless you pass overrides; the spacer-only overload applies.requiredby default.
FixFlex 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:
parent.fx.hstack(Flex([topLabel, bottomLabel]))
parent.fx.vstack(Fill(),
Flex(topLabel),
Fix(5),
Flex(bottomLabel),
Fill())Under the hood, FixFlex creates constraints and layout guides which are equivalent to the following:
topLabel.translatesAutoresizingMaskIntoConstraints = false
bottomLabel.translatesAutoresizingMaskIntoConstraints = false
let layoutGuideTop = UILayoutGuide()
let layoutGuideMiddle = UILayoutGuide()
let layoutGuideBottom = UILayoutGuide()
parent.addLayoutGuide(layoutGuideTop)
parent.addLayoutGuide(layoutGuideMiddle)
parent.addLayoutGuide(layoutGuideBottom)
NSLayoutConstraint.activate([
// hstack
topLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
topLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
bottomLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
bottomLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
//vstack
layoutGuideTop.topAnchor.constraint(equalTo: parent.topAnchor),
layoutGuideTop.bottomAnchor.constraint(equalTo: topLabel.topAnchor),
topLabel.bottomAnchor.constraint(equalTo: layoutGuideMiddle.topAnchor),
layoutGuideMiddle.heightAnchor.constraint(equalToConstant: 5),
layoutGuideMiddle.bottomAnchor.constraint(equalTo: bottomLabel.topAnchor),
bottomLabel.bottomAnchor.constraint(equalTo: layoutGuideBottom.topAnchor),
layoutGuideBottom.bottomAnchor.constraint(equalTo: parent.bottomAnchor),
layoutGuideTop.heightAnchor.constraint(equalTo: layoutGuideBottom.heightAnchor),
])Huh, 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!
parent.fx.hstack(
Fix(15),
Flex(child),
Fix(15)
)
parent.fx.vstack(
Fix(15),
Flex(child),
Fix(15)
)parent.fx.hstack(
Flex(),
Fix(child, 100),
Fix(15)
)
parent.fx.vstack(
Flex(),
Fix(child, 50),
Fix(15)
)parent.fx.hstack(
Fill(),
Fix(child, 100),
Fill()
)
parent.fx.vstack(
Fill(),
Fix(child, 50),
Fill()
)parent.fx.hstack(
Fill(),
Flex(label),
Fill()
)
parent.fx.vstack(
Fill(),
Flex(label),
Fill()
)parent.fx.hstack(Flex([topLabel, bottomLabel]))
parent.fx.vstack(
Fill(),
Flex(topLabel),
Fix(5),
Flex(bottomLabel),
Fill()
)parent.fx.hstack(
Fix(15),
Fix(iconView, 44),
Fix(15),
Flex([titleLabel, subtitleLabel]),
Fix(15),
Fix(chevron, 20),
Fix(15)
)
parent.fx.vstack(
Fix(15),
Fix(iconView, 44),
Flex(min: 15)
)
parent.fx.vstack(
Fix(15),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(15)
)
parent.fx.vstack(
Fill(),
Fix(chevron, 30),
Fill()
)parent.fx.hstack(
Fix(5),
Flex([iconView, titleLabel, subtitleLabel]),
Fix(5)
)
parent.fx.vstack(
Fix(5),
Fix(iconView, 50),
Fix(10),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(5)
)parent.fx.vstack(
Flex([leftLabel, rightLabel])
)
parent.fx.hstack(
Flex(leftLabel, compressionResistancePriority: .required),
Fix(5),
Flex(rightLabel)
)parent.fx.vstack(
Fix(5),
Flex([label1, label2, label3]),
Fix(5)
)
parent.fx.hstack(
Fix(5),
Fill(label1, weight: 2),
Fix(5),
Fill(label2),
Fix(5),
Fill(label3),
Fix(5)
)parent.fx.vstack(
Fix(5),
Flex(label1),
Flex(label2),
Flex(label3),
Fix(5)
)
parent.fx.hstack(
Fix(5),
Flex(label1),
Flex(),
Fix(5)
)
parent.fx.hstack(
Fix(5),
Flex(label2, min: 175),
Flex(),
Fix(5)
)
parent.fx.hstack(
Fix(5),
Flex(label3, max: 100),
Flex(),
Fix(5)
)parent.fx.vstack(
Flex([label, leadingView, trailingView])
)
parent.fx.hstack(
Fill(),
Flex(label),
Fill()
)
parent.fx.hstack(
startAnchor: label.leadingAnchor,
endAnchor: label.trailingAnchor,
Fix(leadingView, 20),
Flex(),
Fix(trailingView, 20)
)parent.fx.vstack(
Flex([label, leadingView, trailingView])
)
parent.fx.hstack(
Fill(),
Flex(label),
Fill()
)
parent.fx.hstack(
startAnchor: label.leftAnchor,
endAnchor: label.rightAnchor,
useAbsolutePositioning: true,
Fix(leadingView, 20),
Flex(),
Fix(trailingView, 20)
)container.fx.vstack(
Fix(10),
Flex(leadingOverflowLabel),
Fix(10),
Flex(trailingOverflowLabel),
Fix(10)
)
container.fx.hstack(
startOffset: nil,
Flex(leadingOverflowLabel)
)
container.fx.hstack(
endOffset: nil,
Flex(trailingOverflowLabel)
)parent.fx.hstack(
Fill(),
Fix(first, 60),
Fix(second, 60).spacingBefore(-20),
Fix(third, 60).spacingBefore(-20),
Fill()
)
parent.fx.vstack(
Fill(),
Fix([first, second, third], 60),
Fill()
)parent.fx.vstack(
Fill(),
Flex(label),
Fill()
)
parent.fx.hstack(
Fill(),
Flex(label),
Fill()
)
parent.fx.vstack(
startAnchor: label.topAnchor,
Fix(10),
Match(matchView, dimension: label.heightAnchor),
Flex()
)
parent.fx.hstack(
startAnchor: label.leadingAnchor,
Fix(10),
Match(matchView, dimension: label.widthAnchor),
Flex()
)Use Swift Package Manager and add dependency to Package.swift file.
dependencies: [
.package(url: "https://github.com/psharanda/FixFlex.git", .upToNextMajor(from: "1.3.0"))
]Alternatively, in Xcode select File > Add Package Dependencies… and add FixFlex repository URL:
https://github.com/psharanda/FixFlex.git
Add github "psharanda/FixFlex" to your Cartfile
FixFlex is available through CocoaPods. To install
it, simply add the following line to your Podfile:
pod "FixFlex"We 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.
FixFlex is available under the MIT license. See the LICENSE file for more info.

















