# How FlowFrameLayout Automatically Wraps Views to the Next Line in FrameLayoutKit

> Discover how FlowFrameLayout automatically wraps views to the next line by dynamically creating new containers when bounds are exceeded. Learn more about this feature in FrameLayoutKit.

- Repository: [Nam Kennic/framelayoutkit](https://github.com/kennic/framelayoutkit)
- Tags: how-to-guide
- Published: 2026-03-05

---

**FlowFrameLayout tracks remaining horizontal space in `calculateSize(fitSize:)` and instantiates new `StackFrameLayout` containers whenever the cumulative view widths exceed the available bounds, creating a dynamic, self-wrapping grid.**

FlowFrameLayout is a dynamic layout container in the open-source **FrameLayoutKit** repository that arranges views in a flowing grid, automatically moving items to the next line when horizontal space runs out. Unlike static collection views, this Swift component performs wrapping calculations during the layout pass using intrinsic content sizes and configurable spacing parameters.

## The Wrapping Algorithm in calculateSize(fitSize:)

The core wrapping logic lives in [`FlowFrameLayout.swift`](https://github.com/kennic/framelayoutkit/blob/main/FlowFrameLayout.swift) within the `calculateSize(fitSize:)` method. When the layout’s `axis` is set to horizontal, the algorithm iterates through the `views` array while maintaining a running tally of available horizontal space using a `remainingSize` variable.

### Measuring and Consuming Space

For each view, the code calls `sizeThatFits(_:)` to determine the view’s intrinsic dimensions. It then subtracts the view’s width plus the `interItemSpacing` value from `remainingSize.width`. This consumption pattern continues sequentially until an overflow condition is detected.

### Detecting Line Breaks (Lines 77–94)

The critical wrapping condition appears around lines 77–94 of [`FlowFrameLayout.swift`](https://github.com/kennic/framelayoutkit/blob/main/FlowFrameLayout.swift). When `remainingSize.width` becomes negative and at least one view has already been placed in the current row (`col > 1`), the algorithm triggers a line break:

```swift
if remainingSize.width < 0, col > 1 {
    // Start a new row
    remainingSize.width = fitSize.width
    remainingSize.height -= result.height
    row += 1
    col = 1
}

```

This reset operation finalizes the current row’s dimensions and initializes a new entry in the `sizeMap` dictionary, which tracks how many items belong to each row using the format `[row: numberOfItems]`.

## Building Rows in layoutSubviews()

After calculating dimensions, `layoutSubviews()` constructs the actual interface using the row mapping generated during the size calculation phase.

### Creating Row Containers

The method instantiates `StackFrameLayout` objects through a `newStack()` helper factory. Each `StackFrameLayout` represents a single row in the flow layout:

```swift
let map = contentSize.map
stackLayout.removeAll()
var index = 0
for i in 0..<map.keys.count {
    let stack = newStack()                 // ← a new row (StackFrameLayout)
    let numberOfItems = map[i+1] ?? 0
    for _ in 0..<numberOfItems {
        let view = views[index]
        stack + view                       // add view to the row
        index += 1
    }
    stackLayout + stack                    // add the row to the vertical scroll stack
    onNewStackBlock?(self, stack)         // callback for each new row
}

```

### The Vertical Scroll Container

`stackLayout` is a `ScrollStackView` configured with a vertical axis. By stacking each `StackFrameLayout` vertically, the component creates the visual appearance of wrapped lines while maintaining a clean parent-child relationship in the view hierarchy.

## Configuration and Callbacks

### Handling Vertical Axis Layouts

When `axis` is set to vertical, the same logic applies but tracks `remainingSize.height` instead. In this orientation, "rows" become columns, and the layout wraps vertically rather than horizontally.

### The onNewStackBlock Callback

The `onNewStackBlock` closure fires each time `layoutSubviews()` creates a new row, allowing developers to customize row appearance or execute side effects during the layout process.

## Complete Implementation Example

The following Swift code demonstrates a horizontal flow layout that automatically wraps buttons to new lines based on screen width:

```swift
import FrameLayoutKit

// Create a flow layout that wraps horizontally
let flow = FlowFrameLayout()
flow.axis = .horizontal               // default – rows flow left-to-right
flow.interItemSpacing = 8
flow.lineSpacing = 12
flow.distribution = .left             // left-aligned rows

// Add views with intrinsic sizes
for i in 0..<20 {
    let btn = UIButton(type: .system)
    btn.setTitle("Item \(i)", for: .normal)
    btn.sizeToFit()                   // establish intrinsic size
    flow.add(btn)
}

// Implementation in a view controller
class DemoVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(flow)
        flow.frame = view.bounds.insetBy(dx: 16, dy: 16)
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        // Recalculates wrapping on rotation or resize
        flow.layoutIfNeeded()
    }
}

```

When the device rotates or the bounds change, `calculateSize(fitSize:)` recomputes the row mappings and `layoutSubviews()` rebuilds the hierarchy with the correct number of items per line.

## Summary

- **FlowFrameLayout** wraps views by monitoring remaining horizontal space during the `calculateSize(fitSize:)` pass
- The overflow condition (`remainingSize.width < 0 && col > 1`) triggers creation of new rows around lines 77–94 in [`FlowFrameLayout.swift`](https://github.com/kennic/framelayoutkit/blob/main/FlowFrameLayout.swift)
- A `sizeMap` dictionary records item counts per row for use during the actual layout phase
- **StackFrameLayout** instances represent individual rows, stacked vertically inside a `ScrollStackView`
- The layout automatically adapts to size changes by recalculating mappings whenever `layoutSubviews()` executes

## Frequently Asked Questions

### What triggers a line break in FlowFrameLayout?

A line break occurs when the cumulative width of views in the current row exceeds the available horizontal space. Specifically, when `remainingSize.width` becomes negative and at least one view is already placed (`col > 1`), the algorithm resets the remaining width counter and increments the row index.

### How does FlowFrameLayout handle different screen sizes?

The layout recalculates row mappings whenever `layoutSubviews()` is called, such as during device rotation or container resizing. The `calculateSize(fitSize:)` method receives the new available bounds and redistributes views across rows accordingly, ensuring content reflows automatically without manual intervention.

### What is the difference between interItemSpacing and lineSpacing?

`interItemSpacing` controls the horizontal gap between views within the same row, while `lineSpacing` determines the vertical distance between separate rows. These properties allow fine-grained control over both the inline distribution and the overall density of the grid layout.

### Can FlowFrameLayout handle vertical wrapping?

Yes, when the `axis` property is set to vertical, the component tracks `remainingSize.height` instead of width and creates columns rather than rows. The wrapping logic remains identical, but views stack vertically and overflow to new columns when height constraints are exceeded.