Open-source project
tevelee/SwiftUI-Flow avatar
tevelee/SwiftUI-Flow

SwiftUI-Flow gives SwiftUI the wrapping layout it never shipped

Flow Layout implemented in SwiftUI

744 stars67 forksSwiftMIT

At a glance

What is it?
SwiftUI-Flow is a Swift package that adds HFlow and VFlow, wrapping containers that break views onto new lines when they run out of room, with Knuth-Plass distribution, per-axis alignment, a flexibility model and a Layout conformance. The trade is a dependency on a third party for something the framework deliberately leaves out.
Who is it for?
SwiftUI-Flow suits a SwiftUI app that needs tags, chips or a wrapping gallery and has decided the layout rules matter enough to want distribution, justification and a flexibility model rather than a hand rolled GeometryReader, and it suits teams that need the same layout on iOS, macOS, tvOS, watchOS and visionOS from one implementation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

HStack and VStack do not wrap, and SwiftUI offers no answer

The gap this package fills is small and specific. SwiftUI gives you HStack and VStack, and SwiftUI-Flow introduces HFlow and VFlow as similar to them, arranging views in lines and cutting new lines accordingly if elements do not fit the bounding space. There is no built-in flow layout in SwiftUI, and the common workaround is a GeometryReader with a manual calculation, which is fiddly, does not compose with the layout system, and quietly breaks under dynamic type and localisation. The intended user is anyone building a tag cloud, a chip row, a wrapping button group or a horizontally scrolling gallery that should fold onto a second line instead of clipping. The package name and the topic list on the repository make the scope clear, and the feature set described in the README is extensive enough that this is a considered implementation rather than a demonstration.

The installation is one line in Package.swift

Adding the package is a single dependency entry, and the requirement list tells you what you are signing up for. The requirements are iOS 16, macOS 13, tvOS 16, watchOS 9 and visionOS 1, with Swift 5.9 and above. The dependency is declared like this:

swift
.package(url: "https://github.com/tevelee/SwiftUI-Flow", from: "3.1.1")

After that you add the product named Flow to your target's dependencies, and the README gives the Xcode route as well, choosing File, then Add Package Dependencies, and entering the repository URL. The full API reference and guides are hosted on the Swift Package Index rather than in the repository, and the repository itself is the source for the package, a Benchmarks directory, an Example Xcode project, a Sources tree, a Tests tree, and both Package.swift and [email protected], which is how the package ships a separate manifest for the Swift 6 language mode.

Two axes, and the same views swap between them

The API is deliberately symmetrical. HFlow wraps rows, VFlow wraps columns, and the two are used the same way, so a layout that has to work in both directions reads the same in either form. The vertical version is the shorter of the two examples in the README:

swift
VFlow {
    ForEach(colors, id: \.description) { color in
        RoundedRectangle(cornerRadius: 10)
            .fill(color.gradient)
            .frame(width: 50, height: .random(in: 40...60))
    }
}
.frame(maxHeight: 300)

The design intent is that the same child views, the same modifiers and the same frame calls work in either container, so switching the axis is a one-word change. That symmetry is also what makes the rest of the feature list coherent, because alignment, spacing and distribution are all described per axis rather than as separate horizontal and vertical features.

Alignment works on both axes at once

The alignment story goes one step beyond what HStack and VStack offer. The README says HFlow and VFlow support the same alignments as HStack and VStack do, and the simple case takes a single alignment value, for example HFlow(alignment: .top). But the additional capability is that alignment can be specified on both axes at once, which the README calls ideal for tags. In practice that means the horizontal alignment controls where each item sits within its own line, and the vertical alignment controls where each line sits within the layout:

swift
HFlow(horizontalAlignment: .center, verticalAlignment: .top) {
    ForEach(colors, id: \.description) { color in
        RoundedRectangle(cornerRadius: 10)
            .fill(color.gradient)
            .frame(width: .random(in: 30...60), height: 30)
    }
}
.frame(maxWidth: 300)

This is the part that makes a tag list look deliberate rather than merely wrapped. With a single alignment, items within a row and rows themselves are tied together, and the result drifts. Two independent axes remove that coupling, and it costs one extra argument.

Item spacing and line spacing are independent knobs

A wrapped layout that uses one spacing value for everything looks wrong, because the gap between items in a row and the gap between rows are different kinds of distance. SwiftUI-Flow separates them, with the README describing customization of spacing between rows and items separately. The parameters are named itemSpacing and rowSpacing:

swift
HFlow(itemSpacing: 4, rowSpacing: 20) {
    ForEach(colors, id: \.description) { color in
        RoundedRectangle(cornerRadius: 10)
            .fill(color.gradient)
            .frame(width: 50, height: 50)
    }
}
.frame(maxWidth: 300)

The same idea carries into separators, which the README says draw dividers between items and or lines, never at the edges, and which take part in sizing and wrapping rather than being drawn over the top. That detail is the difference between a separator that pushes content around and one that overlaps it, and it is the kind of thing a hand rolled implementation tends to get wrong.

Distribution uses Knuth-Plass, which is why lines look even

The most interesting feature is the distribution option, and the algorithm behind it explains what you get. Distribute items evenly by minimizing the empty spaces left in each row, and the README says it implements the Knuth-Plass line breaking algorithm, the same approach used for typesetting paragraphs of text. The difference from a naive greedy wrap is that the algorithm does not simply fill each line until the next item does not fit. It chooses the set of breaks that minimises the ragged space across all lines, which produces a visibly more even result with items of mixed widths. The feature list calls this balance items across lines with the Knuth-Plass algorithm, and the option in the code is distributeItemsEvenly: true. It is worth knowing that the behaviour is about evenness rather than maximum density, since an even layout can leave a larger gap at one edge than a greedy fill would.

Justified lines and the flexibility model are separate concerns

Two more layout behaviours are described separately, and they should not be confused. Justified lines stretch lines to fill the available space, which is the print typographic sense of justified, and it is turned on with justified: true. The flexibility model is about how an individual item participates in the sizing: items can grow, stay rigid, or claim a whole line, and the implementation uses a flexibility modifier with cases. The README shows a minimum case that takes as little space as possible and is rigid, a natural case that expands, and a maximum case that takes as much space as possible. There is also a grow variant, used to distribute leftover space proportionally by weight. The per-item approach is what separates this from a fixed grid, and it is the mechanism that lets a row of chips share the width of a wider row without a hand computed frame on each one.

Line limits, manual breaks and RTL are the details that matter in production

Three smaller features address cases that only show up once a layout is in a real app. Line limits cap a flow to a number of lines with an optional indicator for more items beyond that, which is the usual way to render a tag list that would otherwise be unbounded. Manual line breaks force a break where you want one, either with a LineBreak() element or with a startInNewLine() modifier on an item. And right to left support adapts the layout to the environment's layout direction, so the flow reverses rather than staying left aligned in a right to left locale. There is also Layout conformance, with HFlowLayout and VFlowLayout usable anywhere a Layout is expected, which means the flow can be used inside other custom layout containers rather than only as a top level view.

Editorial conclusion

SwiftUI-Flow suits a SwiftUI app that needs tags, chips or a wrapping gallery and has decided the layout rules matter enough to want distribution, justification and a flexibility model rather than a hand rolled GeometryReader, and it suits teams that need the same layout on iOS, macOS, tvOS, watchOS and visionOS from one implementation. It does not suit a project that only needs a simple two-line wrap, since building the same effect with a plain HStack and a ForEach is shorter than adding a package. Before adopting, confirm the deployment targets, since the package requires iOS 16, macOS 13, tvOS 16, watchOS 9 and visionOS 1 with Swift 5.9, and note that the last push to the repository was on 2026-10-16 with release 3.5.5 tagged the same day.

Frequently asked questions

How do I add SwiftUI-Flow to a Swift project?

Add the package to Package.swift with the entry `.package(url: "https://github.com/tevelee/SwiftUI-Flow", from: "3.1.1")`, then add the Flow product to your target's dependencies. In Xcode you can use File, Add Package Dependencies and enter the repository URL.

What platforms and Swift versions does SwiftUI-Flow support?

The stated requirements are iOS 16 and newer, macOS 13 and newer, tvOS 16 and newer, watchOS 9 and newer, visionOS 1 and newer, with Swift 5.9 and above. The repository also ships a separate [email protected] manifest for the Swift 6 language mode.

What is the difference between HFlow and VFlow?

HFlow wraps rows and VFlow wraps columns, and the two are otherwise used identically, so switching the axis is a one-word change. Both arrange views in lines and cut new lines when the elements do not fit the bounding space.

How does SwiftUI-Flow distribute items evenly across lines?

Setting distributeItemsEvenly to true balances the items across lines by minimizing the empty space left in each row, and the README states that it implements the Knuth-Plass line breaking algorithm, the one used for typesetting paragraphs of text.

Can I control spacing between items and between rows separately?

Yes. The itemSpacing and rowSpacing parameters set the distance between items within a line and the distance between lines independently, and the README describes this as customizing spacing between rows and items separately.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tevelee/SwiftUI-Flow on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tevelee-swiftui-flow.svg)](https://hysenlabs.com/projects/tevelee-swiftui-flow)

Was this useful?