Get Any View To The Center

Jan 10, 2026 SwiftUI

cover image

Centering a view sounds like the most basic layout task you could imagine. And yet, in SwiftUI, it’s one of the first things that forces you to truly understand how the layout system works. SwiftUI doesn’t position views by absolute coordinates. Instead, it negotiates size and position through a parent–child layout conversation. Centering is not a command; it’s a result.

Centering views in SwiftUI is deceptively complex because SwiftUI does not think in terms of coordinates. There is no origin point, no absolute x or y, and no concept of “move this view here.” Instead, SwiftUI operates on negotiation. Every layout decision is the result of a conversation between a parent and its children. Centering is not requested; it emerges.

This is fundamentally different from UIKit and AppKit, where centering is often a constraint or a frame calculation. In SwiftUI, centering happens only when three conditions are satisfied: the parent has more space than the child needs, the child is willing to be smaller than the proposal, and the parent knows how to distribute that leftover space symmetrically.

If even one of these conditions fails, centering fails.

The Layout Algorithm — Why Centering Breaks

SwiftUI layout happens in two passes. First, the parent proposes a size. This proposal can be fixed, flexible, or unbounded. Second, the child chooses its own size within that proposal. The parent then positions the child.

What matters here is that parents never force size, and children never position themselves. This is the core rule most centering bugs violate.

Consider this:

VStack {
    Text("Centered?")
}

This does not center anything because the VStack sizes itself tightly to the text. There is no extra space, so there is nothing to center within. The layout is technically correct.

The moment you add: .frame(maxWidth: .infinity, maxHeight: .infinity) you have changed the proposal. The parent now has excess space, and centering becomes possible.

Understanding this distinction explains nearly every SwiftUI centering issue you’ll ever encounter.

Intrinsic Size vs Proposed Size

Every view has an intrinsic size, the smallest size it can reasonably occupy. Text, images, buttons, toggles—all of them have intrinsic sizes. SwiftUI prefers intrinsic sizes unless explicitly told otherwise.

When a view refuses to shrink, centering fails upstream.

Text("Hello")
    .frame(maxWidth: .infinity)

This text is no longer intrinsically sized. It eagerly expands to fill the proposal. Once a view fills all available space, there is no longer a meaningful “center” relative to its parent. It is the parent.

This is why overusing .frame(maxWidth: .infinity) often breaks centering instead of fixing it. Infinite frames are not neutral; they are aggressive.

Stack-Based Centering

Stacks are layout containers, not alignment tools. Their alignment only applies if they are given space.

VStack(alignment: .center) {
    Text("Hello")
}

The alignment here is meaningless unless the VStack is larger than its content. Alignment does not create space; it only decides what to do with space that already exists.

This is why the canonical centering pattern always looks like this:

VStack {
    Text("Hello")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)

The frame creates space. The stack distributes it. The child accepts its intrinsic size. The result is centering.

This is not accidental; it is the layout system working exactly as designed.

Spacer Is Not “Magic Space”

Spacer is often described as flexible space, but that description hides an important detail: a spacer expands only inside stacks, and it expands after all non-flexible content has taken its intrinsic size.

HStack {
    Spacer()
    Text("Centered")
    Spacer()
}

This works because spacers divide leftover space evenly. If one spacer is removed, the text will shift accordingly. If the text expands infinitely, the spacers collapse to zero.

Spacers are deterministic. They do not guess. If your view is not centering with spacers, something else is already consuming the space.

frame(alignment:) Is a Container, Not a Modifier

The most misunderstood centering technique is this:

Text("Centered")
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)

This works because frame is not just a size modifier. It creates a new layout container. The text remains its intrinsic size, and the frame decides how to place it.

This means: .frame(alignment: .center) does nothing unless the frame has more space than the text needs. Alignment doesn’t work without a specified size.

This container behavior becomes especially important when used inside overlays, backgrounds, and safe area insets.

ZStack: Centering as a Default, Not a Feature

ZStack centers its children because its default alignment is .center. But again, this only works if the ZStack has more space than its children fill in.

ZStack {
    ProgressView()
}

This is not centered unless the ZStack itself expands. Usually, it expands because its parent does, which is why this appears to “just work.”

ZStack is ideal when centering is a structural property of the UI, such as loading indicators, modals, floating buttons, or overlays. When you find yourself fighting spacers, a ZStack might just be the correct abstraction.

GeometryReader

GeometryReader is the escape hatch. The moment you use .position, you are no longer participating in SwiftUI’s layout system.

GeometryReader { proxy in
    Text("Centered")
        .position(
            x: proxy.size.width / 2,
            y: proxy.size.height / 2
        )
}

This produces a visually centered result, but at a cost. The view is no longer aligned, it is placed. This breaks container resizing, and often breaks accessibility focus order.

The rule of thumb is simple: if your centering depends on arithmetic, you are probably doing custom drawing or animation. For UI layout, this should be a last resort.

Alignment Guides: Optical vs Mathematical Centering

Sometimes a view is mathematically centered but visually wrong. This happens often with icons, text with ascenders and descenders, or mixed font sizes.

Alignment guides let you redefine what “center” means.

Text("Title")
    .alignmentGuide(.center) { dimensions in
        dimensions[VerticalAlignment.firstTextBaseline]
    }

This allows views with different visual weights to align in a way that feels centered to the eye rather than the grid. This is subtle, advanced, and extremely powerful, especially in complex typography-heavy layouts.

Scroll Views and the Illusion of Infinite Space

Scroll views do not expand their content; they wrap it.

ScrollView {
    Text("Why am I not centered?")
}

The content height equals the text height. There is no space to center within.

To center content in a scroll view, you must explicitly create minimum space:

ScrollView {
    VStack {
        Text("Empty State")
    }
    .frame(maxWidth: .infinity, minHeight: 400)
}

On iOS, this often ties into safe areas and keyboard avoidance. On macOS, window resizing makes this even more critical. Centering inside scroll views is never implicit; it must be engineered.

UIKit and AppKit: When Legacy Actually Helps

UIKit and AppKit views often already encode centering behavior.

struct CenteredLabel: UIViewRepresentable {
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.textAlignment = .center
        label.text = "UIKit Centered"
        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {}
}

Wrapping these views preserves their internal layout logic. This is especially useful when dealing with text rendering quirks, baseline alignment, or legacy components where SwiftUI equivalents are still lacking.

SwiftUI does not forbid this. It encourages it when correctness matters more than purity.

Why SwiftUI Makes You Work for Centering

SwiftUI’s approach is not accidental friction. It is a forcing function. It makes you understand space, intent, and hierarchy. Once you internalize the layout contract, centering stops being a hack and starts being a guarantee.

The result is UI that survives screen rotations, split views, accessibility settings, window resizing, and future OS changes with minimal effort.

Centering is not trivial in SwiftUI because correctness is not trivial. And once you accept that, the system becomes remarkably consistent.