I am writing a .grouped
-styled Form
on macOS that allows users to edit a list of items. Let's say
struct ContentView: View {
@State private var people = [
Person(name: "Tom"),
Person(name: "Dick"),
Person(name: "Harry"),
]
var body: some View {
NavigationStack {
Form {
Section {
ForEach($people) { $person in
TextField("Name", text: $person.name)
}
Button("New Person") {
people.append(Person(name: ""))
}
}
}
.formStyle(.grouped)
}
}
}
struct Person: Identifiable {
let id = UUID()
var name: String
}
This produces
I don't like how the button at the end looks. I would like the entirety of that "list row" to be a clickable button (much like how Button
s in List
s on iOS look). How can I do that?
The closest I have found is a NavigationLink
.
NavigationLink("New Person") {
EmptyView()
}
produces
Now the whole "list row" is clickable, but I cannot run an arbitrary action. I can only navigate to another view.
To make it look like a NavigationLink
, it requires a custom button style.
When using .grouped
form style, In order to make the highlight on tap cover the entire row, a negative padding needs to be applied to the button, and a positive padding on the button label:
//NavigationLink style button
Button {
people.append(Person(name: ""))
} label: {
HStack {
Text("New Person")
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(systemName: "chevron.right")
.imageScale(.small)
.foregroundStyle(.tertiary)
}
.padding(10)
}
.buttonStyle(NavigationLinkButtonStyle())
.padding(-10)
//Custom button style
struct NavigationLinkButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle())
.background(
configuration.isPressed
? Color(NSColor.separatorColor)
: Color.clear
)
}
}