Categories
Swift SwiftUI

Aligning views in different stacks in SwiftUI

While working on a SwiftUI view, I needed a way for aligning views in different stacks in a way that they are centred. Most of the time I can get away using default alignment values, what HStack and VStack provide. In that case I had two views in different stacks and I needed a way to centre align these views. Here is a view which has three labels in one VStack and 2 texts with an image in another VStack. This is just an example view for illustrating the real world case. The question is, how to align the top text “Hello, world!” with the image so that both are centre aligned.

The answer is to use alignment guides. If combining VStacks and HStacks does not work out, then we can take one step further and define a custom alignment for our views.

private extension VerticalAlignment {
private struct ImageTextAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
static let centeredImageText = VerticalAlignment(ImageTextAlignment.self)
}
view raw View.swift hosted with ❤ by GitHub

That is all what it takes to create a custom vertical alignment. I like to keep custom alignments private, therefore the extension is marked as private.

The next step is hooking it up. We need common ancestor HStack to use the new alignment and the views, which need to be aligned, must use the alignment guide. Here is all the code which illustrates the case.

struct ContentView: View {
var body: some View {
// Parent stack using the custom alignment
HStack(alignment: .centeredImageText) {
VStack(alignment: .leading) {
Text("Hello, world!")
.font(.largeTitle)
// Guide for text in the first VStack
.alignmentGuide(.centeredImageText, computeValue: { dimension in
dimension[.centeredImageText]
})
Text("Hi hi!")
.font(.callout)
.foregroundStyle(.secondary)
Text("Another line")
.font(.callout)
.foregroundStyle(.tertiary)
}
VStack(alignment: .leading) {
Text("Another label")
.font(.system(.callout))
Text("Another label")
.font(.system(.callout))
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
// Guide for image in the second VStack
.alignmentGuide(.centeredImageText, computeValue: { dimension in
dimension[.centeredImageText]
})
}
}
.padding()
}
}
view raw View.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.