How it works
EditButton is a built-in control that toggles the edit mode of its surrounding scope, flipping its title between Edit and Done as it does so. Reach for it whenever you present a List whose rows the user should be able to delete or reorder: rather than wiring up your own state to track an editing session, EditButton drives the standard editMode environment value for you, and the List responds by revealing its delete and move affordances. It carries no configuration of its own, which makes it the idiomatic way to expose row editing in a navigation bar or toolbar.
Instantiate EditButton with its no-argument initializer
EditButton takes no parameters; you create it by calling
EditButton(). The label and action are supplied for you, so there is nothing to configure at the call site. Because it is a View, you place it wherever you want the toggle to appear.Host it in a toolbar so it sits in the navigation bar
EditButton is most at home in a bar, where it can stand in for the conventional system Edit control. Here it is placed inside
.toolbar { EditButton() }on aNavigationStack, which positions it in the navigation bar alongside the.navigationTitle("Fruits").Let it drive the editMode environment value
EditButton's only job is to flip the
editModevalue in the environment between inactive and active. Every view inside that environment, including theListand itsForEach, reads this value to decide whether to show its editing UI; you never set or readeditModeyourself when you use EditButton.Pair it with editable List rows
EditButton has no effect on its own, it needs rows that opt into editing. Attaching
.onDeleteand.onMoveto theForEachis what makes deletion and reordering available, and EditButton is the control that surfaces them. In this list,.onDelete { fruits.remove(atOffsets: $0) }removes a fruit and.onMove { fruits.move(fromOffsets: $0, toOffset: $1) }reorders them.Rely on its automatic title and state
Once tapped, EditButton swaps its own title to Done and the
Listshows delete badges and drag handles; tapping again restores normal mode. This active/inactive presentation is managed entirely by EditButton, so the@State private var fruitsarray stays the single source of truth while the button manages the editing session.
.onMove { fruits.move(fromOffsets: $0, toOffset: $1) } modifier from the ForEach and tap Edit, the drag handles disappear while delete still works, showing that EditButton only surfaces the editing actions the rows actually provide.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct EditButtonDemo: View {
@State private var fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
NavigationStack {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
.onDelete { fruits.remove(atOffsets: $0) }
.onMove { fruits.move(fromOffsets: $0, toOffset: $1) }
}
.navigationTitle("Fruits")
.toolbar {
EditButton()
}
}
.padding()
}
}