Article

SwiftUI Custom Alignment

Paul Himes

September 18, 2023

An animation of a three bookshelves. On the middle shelf, some books start to slide and fall over. A tiny robot pushes the books back into place.
Updated for Xcode 14.3

SwiftUI comes with many built-in options for aligning views. When you use the basic container views such as HStack, VStack, and ZStack you have the option to provide an alignment parameter which controls the placement of subviews relative to each other. Alignments are categorized as either HorizontalAlignment or VerticalAlignment.

A container’s available alignments are those perpendicular to the predominant axis of the container view. For example, HStack lays out subviews horizontally, so it allows VerticalAlignment. VStack lays out subviews vertically, so it allows HorizontalAlignment. ZStack lays out subviews along both vertical and horizontal axes, so it uses a compound Alignment type which contains both a HorizontalAlignment and VerticalAlignment.

Each alignment type comes with a collection of standard alignments. For example, the standard alignments for VerticalAlignment are: top, center, bottom, firstTextBaseline, and lastTextBaseline.

Three VerticalAlignment diagrams for standard top, center, and bottom alignments.
Some Standard VerticalAlignments

These pre-defined alignments can be used to create most typical layout designs. But what if a design calls for views to be placed at a location other than one of the standard alignments?

A cyan ZStack with a “Hello, SwiftUI!” text label aligned to the leading edge and vertically centered at two-thirds the height of the container.
A Custom Alignment

For this, SwiftUI allows us to define our own custom alignments. Let’s see how we can define a custom VerticalAlignment that places views two-thirds of the way down a ZStack.

Custom Alignment Implementation

The built-in alignment options are defined as static properties on the corresponding alignment types. Here’s how VerticalAlignment defines it’s top alignment value.

extension VerticalAlignment {
public static let top: VerticalAlignment
}

Let’s define our two-thirds alignment in a similar way.

extension VerticalAlignment {
public static let twoThirds = VerticalAlignment(…)
}

You’ll notice we actually have to initialize a VerticalAlignment when we declare our custom alignment value. The initializer takes a single parameter of type AlignmentID.Type. We’ll need to define an AlignmentID for our custom alignment. This type is never meant to be instantiated, so we’ll define it as an enum.

enum TwoThirdsAlignmentId: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {

}
}

The AlignmentID protocol requires a single static function named defaultValue(in:). This function contains our custom layout calculations. It receives a ViewDimensions value which provides sizing information about the View we’re aligning. It’s also responsible for returning an offset in the view’s coordinate space. So returning 0 for a vertical alignment would align to the top edge of the view. Returning the height of the view would align to the bottom edge of the view. For our two-thirds alignment, we want to align to two-thirds of the view’s height.

enum TwoThirdsAlignmentId: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context.height * 2/3
}
}

Now we can use our AlignmentID with our VerticalAlignment.

extension VerticalAlignment {
public static let twoThirds = VerticalAlignment(TwoThirdsAlignmentId.self)
}

Since we want to use this custom alignment with a ZStack, there’s one more static property to define. Remember that ZStack uses the Alignment type which combines a VerticalAlignment and a HorizontalAlignment. In this example, we’ll use a leading horizontal alignment. So let’s define a twoThirdsLeading alignment like this:

extension Alignment {
static let twoThirdsLeading = Alignment(horizontal: .leading, vertical: .twoThirds)
}

Using a Custom Alignment

Now we’re ready to use our custom alignment in a ZStack. Let’s start by building a SwiftUI View with a ZStack and some text content.

struct Example: View {
var body: some View {
ZStack {
Color.cyan
TitleView()
RedLine()
}
}
}
A cyan ZStack with a “Hello, SwiftUI!” text label in the center. A horizontal red line crosses the center.
A Default ZStack

Our ZStack contains three views: a background Color view, a TitleView, and a RedLine view. The Color view expands the ZStack to use all available space. This will make it easier for us to see how the content views are being aligned. The RedLine view will also be used to show us exactly where views are being aligned vertically. By default, ZStack uses center alignment to place all content views in the center of the container. Let’s see what happens when we use our new custom alignment.

struct Example: View {
var body: some View {
ZStack(alignment: .twoThirdsLeading) {
Color.cyan
TitleView()
RedLine()
}
}
}
A cyan ZStack with a “Hello, SwiftUI!” text label aligned to the leading edge at two-thirds height. A horizontal red line crosses the ZStack at two-thirds of its height.
ZStack with Custom Alignment

Our custom alignment is working! We can see that all three content views are aligned to two-thirds of their heights. But there’s one more change we need to make to match our original design. Rather than pinning the TitleView on the RedLine at two-thirds of its height, we actually want the TitleView to be centered vertically on the RedLine. To achieve this, we need to adjust how TitleView responds to our custom VerticalAlignment.twoThirds alignment. This can be done with SwiftUI’s alignmentGuide view modifier.

struct Example: View {
var body: some View {
ZStack(alignment: .twoThirdsLeading) {
Color.cyan
TitleView()
.alignmentGuide(.twoThirds) { context in

}
RedLine()
}
}
}

The alignmentGuide modifier is very similar to the defaultValue function of AlignmentID. In the first parameter, we specify which alignment we’re modifying. In this case, it’s our custom twoThirds alignment. Then we provide a closure which receives a ViewDimension describing the sizing of the TitleView. In this closure, we’re again responsible for returning an offset in the view’s coordinate space. Instead of returning two-thirds of the view’s height, we’ll now return half of the view’s height.

struct Example: View {
var body: some View {
ZStack(alignment: .twoThirdsLeading) {
Color.cyan
TitleView()
.alignmentGuide(.twoThirds) { context in
context.height / 2
}
RedLine()
}
}
}
A cyan ZStack with a “Hello, SwiftUI!” text label aligned to the leading edge and vertically centered at two-thirds the height of the container. A horizontal red line crosses the ZStack at two-thirds of its height.
Text Vertically Centered at Two-Thirds

And we’re done! The TitleView is now vertically centered at two-thirds of the height of its container. Try experimenting with different combinations of standard alignments, custom alignments, and alignmentGuide to see what layouts you can come up with.

Thanks for reading!



Paul works at Livefront , where the test coverage is strong, the designs are good looking, and all the developers are above average.