The ControlGroup container (SwiftUI Release 3, iOS 15+) displays semantically related
controls, such as an increase and decrease pair, with a look chosen for its context. Use it
instead of a bare HStack when the buttons form one logical unit.
Basics
Put the controls in the ViewBuilder closure, the same way as a stack. The group draws them as
one visual cluster whose style depends on placement.
ControlGroup {
Button(action: {}) {
Label("Decrease", systemImage: "minus")
}
Button(action: {}) {
Label("Increase", systemImage: "plus")
}
}
Built-in Styles
SwiftUI ships automatic and navigation styles. automatic is the default;
SwiftUI applies navigation on its own when the group sits in a NavigationView toolbar, and
controlGroupStyle(.navigation) sets it by hand.
Custom Styles
Conform to ControlGroupStyle and implement makeBody(configuration:). The configuration's
content is the group's children, so the style decides the layout and chrome around them.
struct ControlGroupWithTitle: ControlGroupStyle {
let title: LocalizedStringKey
func makeBody(configuration: Configuration) -> some View {
VStack {
Text(title)
.font(.title)
HStack {
configuration.content
}
}
}
}
Swift 5.5 extensions on the style protocol give the custom style dot syntax at the call site:
extension ControlGroupStyle where Self == ControlGroupWithTitle {
static func with(title: LocalizedStringKey) -> ControlGroupWithTitle {
ControlGroupWithTitle(title: title)
}
}
ControlGroup {
Button("Action 1") {}
Button("Action 2") {}
}
.controlGroupStyle(.with(title: "Actions"))
This is the same styling pattern as ButtonStyle and the other style protocols: learn it once
and it applies across SwiftUI's containers.