TechnologiesSwiftUI

EditButton struct

iOSmacOStvOSwatchOSvisionOS✓ renders

A button that toggles the edit mode environment value.

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.

  1. 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.

  2. 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 a NavigationStack, which positions it in the navigation bar alongside the .navigationTitle("Fruits").

  3. Let it drive the editMode environment value

    EditButton's only job is to flip the editMode value in the environment between inactive and active. Every view inside that environment, including the List and its ForEach, reads this value to decide whether to show its editing UI; you never set or read editMode yourself when you use EditButton.

  4. Pair it with editable List rows

    EditButton has no effect on its own, it needs rows that opt into editing. Attaching .onDelete and .onMove to the ForEach is 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.

  5. Rely on its automatic title and state

    Once tapped, EditButton swaps its own title to Done and the List shows delete badges and drag handles; tapping again restores normal mode. This active/inactive presentation is managed entirely by EditButton, so the @State private var fruits array stays the single source of truth while the button manages the editing session.

Try it — Remove the .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.

EditButton.swift
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()
    }
}
Live preview
fruit fruit fruit 9:41 Fruits
fruit fruit fruit 9:41 Fruits
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →