---
title: "Layout"
description: "The Layout protocol lets you build custom layout algorithms in SwiftUI."
url: https://www.swiftuifieldguide.com/layout/layout
markdown_url: https://www.swiftuifieldguide.com/layout/layout.md
---
> Convenience Markdown export of the HTML page. Interactive samples, diagrams, and visualizations stay on the canonical page.
# The Layout Protocol

The `Layout` protocol is very useful when you want to switch between different layouts without recreating views. It allows the views to maintain their identity as you switch layouts, and it lets you animate between different layouts.

> Pretty-printed code from Sample0

```swift
AnyLayout(HStackLayout()) {
    Color.orange
    Capsule()
        .fill(.yellow)
    Text("Hello")
}
.padding()
```

> Interactive example on HTML page: Sample0.

It's also possible to create your own layouts. The [Layout](https://developer.apple.com/documentation/swiftui/layout) protocol lets you implement your own layout algorithm. The two essential methods to implement are `sizeThatFits(proposal:subviews:cache:)` and `placeSubviews(in:proposal:subviews:cache:)`. The first method is used to compute the size of the entire layout, and the second method let you set the position of the individual subviews.

## Building a Flow Layout {#flowLayout}

One of the things missing in vanilla SwiftUI is a default flow layout. Here is a very basic implementation with configurable spacing. Note that, as with any other layout, the flow layout only becomes as large as needed (it doesn't necessarily fill up the available space).

> Pretty-printed code from Sample1

```txt
ScrollView {
    AnyLayout(FlowLayout(spacing: )) {
        /* All of the names */
    }
    /* .border(.red) */
    .padding()
}
```

> Interactive example on HTML page: Sample1.

To build this layout, we start by creating a standalone method that lays out views. This `layout` method does not need to know anything about the actual views except for their sizes. It iterates over the views and places them on the current line. If the view does not fit on the current line, it starts a new line. The function returns both the positions of the views and the size of the entire layout.

> Pretty-printed code from Custom Layout Code

```swift
func layout(sizes: [CGSize],
             spacing: CGFloat = 8,
             containerWidth: CGFloat) ->
     (offsets: [CGPoint], size: CGSize) {
     var result: [CGPoint] = []
    var currentPosition: CGPoint = .zero
    var lineHeight: CGFloat = 0
    var maxX: CGFloat = 0
     for size in sizes {
        if currentPosition.x + size.width
             >
             containerWidth {
             currentPosition.x = 0
             currentPosition.y +=
                 lineHeight + spacing
             lineHeight = 0
         } 
         result.append(currentPosition)
         currentPosition.x += size.width
        maxX = max(maxX, currentPosition.x)
         currentPosition.x += spacing
         lineHeight = max(lineHeight, size.height)
     }
     return (result,
         .init(width: maxX,
               height: currentPosition.y
                   +
                   lineHeight))
 }
```

> Interactive example on HTML page: Custom Layout Code.

The `FlowLayout` struct can conform to the layout protocol by implementing the two required methods. To do so, it first gathers the ideal sizes of all the subviews. The `layout` function directly returns the size needed.

> Pretty-printed code from Custom Layout Impl

```swift
struct FlowLayout: Layout {
     var spacing: CGFloat = 8
 
     func sizeThatFits(proposal: ProposedViewSize,
                       subviews: Subviews,
                       cache: inout ()) -> CGSize {
        let containerWidth =
             proposal.width ?? .infinity
         let sizes =
             subviews.map {
                 $0.sizeThatFits(.unspecified)
             }
         return layout(sizes: sizes,
                       spacing: spacing,
                       containerWidth: containerWidth).size
     }
     
     func placeSubviews(in bounds: CGRect,
                        proposal: ProposedViewSize,
                        subviews: Subviews,
                        cache: inout ()) {
         let sizes =
             subviews.map {
                 $0.sizeThatFits(.unspecified)
             }
         let offsets =
             layout(sizes: sizes,
                    spacing: spacing,
                    containerWidth: bounds.width).offsets
         for (offset, subview) in zip(offsets,
                                      subviews) {
             subview.place(at: .init(x: offset.x
                                         +
                                         bounds.minX,
                                     y: offset.y
                                         +
                                         bounds.minY),
                           proposal: .unspecified)
         }
     }
 }
```

> Interactive example on HTML page: Custom Layout Impl.

When placing the subviews, we can use the offsets returned from the layout function to place each subview. We make sure to place it within bounds by adding the `minX` and `minY` values, respectively.

> Pretty-printed code from Custom Layout Impl

```swift
struct FlowLayout: Layout {
     var spacing: CGFloat = 8
 
     func sizeThatFits(proposal: ProposedViewSize,
                       subviews: Subviews,
                       cache: inout ()) -> CGSize {
        let containerWidth =
             proposal.width ?? .infinity
         let sizes =
             subviews.map {
                 $0.sizeThatFits(.unspecified)
             }
         return layout(sizes: sizes,
                       spacing: spacing,
                       containerWidth: containerWidth).size
     }
     
     func placeSubviews(in bounds: CGRect,
                        proposal: ProposedViewSize,
                        subviews: Subviews,
                        cache: inout ()) {
         let sizes =
             subviews.map {
                 $0.sizeThatFits(.unspecified)
             }
         let offsets =
             layout(sizes: sizes,
                    spacing: spacing,
                    containerWidth: bounds.width).offsets
         for (offset, subview) in zip(offsets,
                                      subviews) {
             subview.place(at: .init(x: offset.x
                                         +
                                         bounds.minX,
                                     y: offset.y
                                         +
                                         bounds.minY),
                           proposal: .unspecified)
         }
     }
 }
```

> Interactive example on HTML page: Custom Layout Impl.

There are a number of ways in which we can improve the layout above. For example, we could have separate values for horizontal and vertical spacing. We could control the vertical alignment within each line, as well as the horizontal alignment of multiple lines.

We recorded a number of Swift Talk episodes about flow layouts, [episode 308](https://talk.objc.io/episodes/S01E308-the-layout-protocol) and [episode 350](https://talk.objc.io/episodes/S01E350-flow-layout-alignment) are the most relevant.
