macOS 13 only
The new LabeledContent
view in SwiftUI will help you:
LabeledContent("Label") {
MenuButton("Menu") {
Button(action: {
print("Clicked Pizza")
}) { Text("Pizza") }
Button(action: {
print("Clicked Pasta")
}) { Text("Pasta") }
}
TextField("Topping", text: .constant("Cheese"))
.labelsHidden()
}
}

However, this view has not been backported to earlier versions of macOS, so if you need to support earlier versions you'll need another approach.
Earlier versions of macOS
Building on the preference key code from @Nhat Nguyen Duc, the key is to use alignment guides rather than padding. Creating a custom view, and with a customised preference that only measures the width:
struct LabeledHStack<Content: View>: View {
var label: String
var content: () -> Content
@State var labelWidth: CGFloat = 0
init(_ label: String, @ViewBuilder content: @escaping () -> Content) {
self.label = label
self.content = content
}
var body: some View {
HStack {
Text(label)
.readSize { self.labelWidth = $0 }
content()
}
.alignmentGuide(.leading) { _ in labelWidth + 10 } // see note
}
}
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { }
}
extension View {
func readWidth(onChange: @escaping (CGFloat) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: WidthPreferenceKey.self, value: geometryProxy.size.width)
}
)
.onPreferenceChange(WidthPreferenceKey.self, perform: onChange)
}
}
Note that in the custom view I've added 10 pixels to quickly emulate the spacing between a label and its form elements. There is probably a better way to make this work for accessibility sizes, etc., (e.g., the use of a @ScaledMetric
value). You might also wish to apply this as padding rather than in the alignment guide calculation.
Below has a line with macOS13's LabeledContent
, followed by LabeledHStack
:
