I'm struggling to achieve the following custom alignment in a SwiftUI VStack:
I've created a custom alignment guide to use in the Event view, as follows:
extension HorizontalAlignment {
struct EventLeading: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[HorizontalAlignment.leading]
}
}
static let eventLeading = HorizontalAlignment(EventLeading.self)
}
struct Event: View {
let date: String
let event: String
var body: some View {
HStack(alignment: .firstTextBaseline) {
Text(date)
.font(.caption)
Text(event)
.alignmentGuide(.eventLeading) { d in d[.leading]}
}
}
}
However, in the same VStack are Divider views between adjacent Events, and these do not specify the eventLeading alignment guide and assume the default (.leading in this case):
struct VStackAlignedToGuide: View {
var body: some View {
VStack(alignment: .eventLeading) {
Event(date: "12 Oct 1568", event: "Born")
Divider()
Event(date: "2 Feb 1612", event: "Ate porridge for breakfast")
Divider()
Event(date: "1613", event: "Lost a shoe")
}
}
}
let test = VStackAlignedToGuide()
.frame(width: 400)
PlaygroundPage.current.setLiveView(test)
This causes the events to shift left and the dividers to shift right:
Is there a way to achieve this simply and cleanly? I've read lots of articles online. This excellent guide has one possible solution I haven't tried... but my layout requirement seems so simple there must be an easier way!
I thought I might manage it by expanding the frame of the event to the width of the container, and then using the alignment guide within the frame. That way it would not interfere with the alignment of the VStack. I've tried lots of combinations without success. This is one:
struct Event: View {
let date: String
let event: String
var body: some View {
HStack(alignment: .firstTextBaseline) {
Text(date)
.font(.caption)
Text(event)
.alignmentGuide(.eventLeading) { d in d[.leading]}
}
.border(.blue)
.frame(maxWidth: .infinity,
alignment:
Alignment(horizontal: .eventLeading,
vertical: .top))
.border(.black)
}
}
This fails because the .eventLeading alignment guide in the individual Events is aligned to the leading edge of the container:
Any help very much appreciated. Thank you.



iOS 16 and later
If your deployment target is iOS 16 (released in 2022) or later, use
Grid:Result:
Older than iOS 16
There's no elegant way to do this on older versions of iOS. Apple didn't add good layout tools (like
GridandLayout) until iOS 16.One hacky way to do it is to put short, hidden dividers in the
VStackand collect theirypositions. Then, overlay theVStackwith visible dividers at those positions. The visible dividers aren't inside theVStackso they aren't subject to its alignment, and they pick up its width automatically. However, you cannot set a visible divider's y position without also setting its x position (lest the divider's x position be set to zero), so you also need to pick up theVStack's x position.We'll use the
eventLeadingalignment you defined already:We also need a structure to collect the y positions of the hidden dividers and the x position of the
VStack:To actually collect the positions, we need to use SwiftUI's
preferencemodifier, which means we need a type that conforms toPreferenceKey. We might as well useDividerLayoutInfofor that too:We'll also need a name for the
coordinateSpacefrom which we collect positions and in which we lay out the visible dividers:We'll overlay the
VStackwith this view to copy theVStack's x center position into a preference:We'll use this view to place each hidden divider into the
VStackand copy its y center into a preference:We'll overlay the
VStackwith this view to draw the visible dividers:Finally, here's the view that puts it all together, using
overlayPreferenceValueto access the collectedDividerLayoutInfo:Result:
Here's all the code together for your convenience: