To keep a Mac window’s label and status columns steady while its description expands, give the outer columns explicit widths and the description a flexible frame. Use layout priority when siblings compete, and preserve ideal size only on the axes that need it.
This tutorial builds a small, resizable window containing synthetic item labels and descriptions. The task is to keep columns aligned while allowing longer descriptions to wrap as the window narrows. Everything belongs to the example app; the layout uses ordinary SwiftUI views.
Which sizing control should each subview use?
SwiftUI layout begins with a parent proposing a size. Each child responds with its preferred size, and the container arranges the results. A useful starting point is to decide which dimensions each child may change. Apple explains this proposal-and-response process in A guide to layout in SwiftUI.
For this window, assign three roles: a label column with a chosen width, a description column that absorbs width changes, and a status column with a chosen width. Distinguish that numeric fixed width from a view’s ideal size: they express different requirements.
| Requirement | Control | Practical consequence |
|---|---|---|
| Reserve a specific column width | .frame(width: 96) |
The resulting frame has that width. |
| Let a description absorb available width | .frame(minWidth: 160, maxWidth: .infinity, alignment: .leading) |
The frame accepts the proposed width subject to its minimum. |
| Preserve content’s ideal size on selected axes | .fixedSize(horizontal: false, vertical: true) |
Width can respond to layout while height preserves its ideal size. |
| Favor one sibling during space allocation | .layoutPriority(1) |
The parent gives that child preference over lower-priority siblings. |
Apple documents numeric dimensions in the fixed frame modifier, minimum and maximum constraints in the flexible frame modifier, axis-specific ideal sizing in fixedSize(horizontal:vertical:), and sibling preference in layoutPriority(_:).
An infinite maximum is permission to expand within a proposal; it does not request an infinitely wide window. Providing both minimum and maximum constraints makes the frame adopt the proposed dimension, clamped to those constraints. The alignment parameter positions the child inside that frame. These details matter when the text itself is narrower than the space assigned to its column. Apple’s flexible frame documentation
Build a complete resizable macOS example
Create a macOS app project in Xcode using SwiftUI and set its deployment target to macOS 13 or later. Replace the generated app and content-view declarations with this single file, leaving only one app entry point. The example uses macOS 13’s window resizability control. Apple’s WindowResizability documentation
The following is a complete app example. It includes its sample strings and a toggle for switching between short and long descriptions. The SwiftUI app could not be compiled in the research environment because a Swift toolchain was unavailable; inspect its behavior in Xcode before integrating it into an existing project.
import SwiftUI
@main
struct ColumnAllocationApp: App {
var body: some Scene {
WindowGroup {
AllocationView()
.frame(minWidth: 420, minHeight: 260)
}
.windowResizability(.contentMinSize)
}
}
struct AllocationView: View {
@State private var showLongDescriptions = true
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Item overview")
.font(.title2)
Toggle(
"Show longer descriptions",
isOn: $showLongDescriptions
)
ScrollView {
VStack(alignment: .leading, spacing: 16) {
AllocationRow(
label: "Item A",
summary: showLongDescriptions
? "A blue folder with several sample notes and a longer descriptive label."
: "Blue folder",
status: "Ready"
)
AllocationRow(
label: "Item B",
summary: showLongDescriptions
? "A green notebook containing a short list of synthetic example entries."
: "Green notebook",
status: "Draft"
)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(16)
}
}
struct AllocationRow: View {
let label: String
let summary: String
let status: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Text(label)
.frame(width: 96, alignment: .leading)
Text(summary)
.fixedSize(horizontal: false, vertical: true)
.frame(
minWidth: 160,
maxWidth: .infinity,
alignment: .leading
)
Text(status)
.fixedSize(horizontal: true, vertical: false)
.frame(width: 72, alignment: .trailing)
}
}
}
The description is the only horizontally flexible column. Its fixedSize call leaves horizontal sizing responsive while preserving the vertical space needed by the wrapped text. Its flexible frame then fills the allocated column. The status preserves its ideal text width inside a separate 72-point frame. Apple cautions that ideal-size content can extend beyond its parent’s bounds, so that status treatment assumes the supplied short values, Ready and Draft. Apple’s fixedSize documentation
The rows have no fixed height. This is deliberate: narrower descriptions can require more lines. The scroll view provides a way to reach rows when their combined height exceeds the available area. Avoid adding a restrictive row height merely to make the initial arrangement look uniform.
The window uses .contentMinSize, which derives its minimum dimensions from the content and imposes no maximum size. Here the root’s 420-point minimum width establishes a practical lower bound for the example. This control applies to windows created by the app’s scene. Apple’s contentMinSize documentation
The declarations returning some View use Swift opaque result types: their concrete types remain hidden while their conformance to View remains available. You can adjust the modifier chains without spelling out the nested return types. The Swift Programming Language: Opaque and Boxed Protocol Types
Calculate the width budget before changing priorities
A width budget makes the layout’s limits explicit. In this example, subtract the horizontal padding, both gaps, and the two fixed columns from the window’s content width. The remainder belongs to the description. These are illustrative design dimensions, not measurements of native controls.
Description width
= content width - horizontal padding - gaps - fixed columns
= content width - (2 × 16) - (2 × 12) - 96 - 72
= content width - 224
At 640 points: 416 points remain for the description.
At 420 points: 196 points remain for the description.
Minimum row budget:
32 + 24 + 96 + 160 + 72 = 384 points.
The arithmetic was checked with a deterministic local calculation. It describes this row when it receives the content width minus the stated padding; it excludes window decorations. The chosen 420-point window minimum leaves room above the row’s calculated 384-point minimum. If a surrounding container adds insets, include those in the budget too.
This calculation also identifies an impossible request. Below the combined minimum budget, all three columns cannot retain their widths and gaps simultaneously. Choose a larger minimum window width, smaller minimum dimensions, or a different arrangement. Increasing priority cannot create additional space.
When does layout priority help?
Priority becomes useful when two siblings can both shrink and you want one to retain more space. For example, a descriptive title may deserve preference over a secondary annotation. Put .layoutPriority(1) on the child participating in that parent’s allocation decision. Apple states that higher-priority views shrink later and stretch sooner, with space reserved for the minimum requirements of lower-priority children. Apple’s layoutPriority documentation
The complete example needs no priority modifier: its allocation policy is already expressed through two fixed columns and one flexible column. A priority value is also not a proportional weight. Assigning priorities of 2 and 1 does not establish a two-to-one width ratio; the documented purpose is preference during allocation. Apple’s layoutPriority documentation
Avoid adding a horizontal Spacer between the description and status unless you want a separate flexible gap. A spacer expands along its stack’s main axis and pushes siblings apart. Because the description’s frame already absorbs width changes, that additional participant is unnecessary here. Apple’s Spacer documentation
What should happen when text no longer fits?
Decide separately whether each text field should wrap, truncate, or require a different arrangement. The example descriptions may wrap without a line limit. Applying .lineLimit(1) would cap each affected text element at one line, changing the requirement. A line limit of nil removes that cap, but does not remove constraints imposed by enclosing frames. Apple’s lineLimit documentation
For longer status values, reconsider the 72-point column. Remove horizontal ideal-size preservation if wrapping is acceptable, increase the column width, or move the status below the description. Keeping .fixedSize(horizontal: true, vertical: false) while narrowing its enclosing frame can allow the text to extend beyond that frame. Apple’s fixedSize documentation
If a compact window should rearrange the content, macOS 13 and later provide ViewThatFits. Supply a horizontal arrangement first and a stacked arrangement second. It selects the first child whose ideal size fits on the constrained axes, so evaluate horizontal space with ViewThatFits(in: .horizontal) when width is the deciding factor. Make the final candidate usable at your supported minimum width. Apple’s ViewThatFits documentation
Treat that as an alternate layout policy. The current example deliberately retains columns and establishes a minimum window width. Use a stacked fallback when maintaining the horizontal arrangement matters less than allowing a narrower window.
How do embedded AppKit and TextKit views change the approach?
If the description later becomes an AppKit text component, use NSViewRepresentable to integrate the view. Apple explicitly says SwiftUI controls the represented view’s frame and bounds; directly changing those properties from your own code conflicts with SwiftUI layout. Configure the content through the representable lifecycle and express outer sizing through SwiftUI. Apple’s NSViewRepresentable documentation
On macOS 13 and later, the representable’s sizeThatFits(_:nsView:context:) method can return its preferred size for a proposal. It may be called several times with different proposals in one layout pass. Returning nil requests the default sizing algorithm. A wrapping component’s measurement should account for the proposed width rather than always returning its unwrapped text width. Apple’s representable sizing documentation
Keep unspecified dimensions distinct from zero: SwiftUI uses an unspecified proposal to ask for ideal size, while zero can probe minimum size. A custom measurement routine must handle those cases deliberately. Apple’s ProposedViewSize documentation
Inside an AppKit layout, compression resistance controls how strongly an NSView resists becoming smaller than its intrinsic size. Inspect that setting when a wrapped component resists compression, alongside its SwiftUI sizing behavior. Apple’s setContentCompressionResistancePriority documentation
For an NSTextView using TextKit 2, use the modern textLayoutManager path. Apple’s current TextKit overview distinguishes it from the legacy layoutManager, and its migration guidance explains that accessing the legacy property can trigger a one-way compatibility switch to TextKit 1 for that instance. Changing text engines is unnecessary for allocating sibling columns. Apple’s TextKit overview, What’s new in TextKit and text views
Check the result at the window’s limits
Run the example and inspect these conditions before applying the pattern to a larger interface:
- Widen the window: the description column should expand while the label and status frames retain their widths.
- Narrow it to the supported minimum: the longer descriptions should wrap and increase row height.
- Switch
Show longer descriptionsoff and on: short descriptions should remain aligned with the same columns. - Substitute a longer synthetic status: decide whether to widen, wrap, or rearrange that field.
- Reduce available height: confirm that all rows remain reachable.
To see which layer receives space, temporarily add borders immediately before and after the description’s flexible frame. The inner border reveals the text’s bounds; the outer border reveals its allocated column. Apple recommends colored borders as a practical way to diagnose stack sizing and padding. A guide to layout in SwiftUI
Keep the sizing policy explicit as the interface grows: reserve widths where alignment requires them, give variable content a defined flexible region, and decide how each field behaves at the minimum width. With that policy and a width budget, you can change labels or add content without guessing which modifier should receive a larger priority.
Sources
- A guide to layout in SwiftUI — Apple
- frame(width:height:alignment:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- layoutPriority(_:) — Apple
- WindowResizability — Apple
- contentMinSize — Apple
- Opaque and Boxed Protocol Types — Swift.org
- Spacer — Apple
- lineLimit(_:) — Apple
- ViewThatFits — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- ProposedViewSize — Apple
- setContentCompressionResistancePriority(_:for:) — Apple
- TextKit — Apple
- What’s new in TextKit and text views — Apple