DEV Community

MZ
MZ

Posted on

editMode environment variable

@Environment(\.editMode) var editMode is more of a binding rather than a value. A lot of tutorials touch on it as if it already has a value but it is actually a binding variable, meaning someone has to give it a value.

The usual use case is usually when dealing with Lists and EditButton(). In this case, however, there is no explicit declaration of @Environment(\.editMode) var editMode anywhere in the code because List provides a value to editMode internally so we dont really see it happening. So now, we can safely attribute each element to a function.

Namely:
1) EditButton() simply toggles editMode, the environment variable, when the user taps on it
2) List sees this change (because it tracks editMode) and turns on edit mode. It switches the text on the edit button from "Edit" to "Done" and surfaces the usual UI for deleting entries.

Even though editMode was never declared explicitly, it is still the single source of truth.

That's the usual use case. However, if you want manual control, there is another way.

@Environment(\.editMode) var editMode as a binding suggests that it would be declared in a child view. So, how do we give it value? We do up our own "edit mode" variable in the parent view.

This could look like

@State private var editMode: EditMode = .inactive
Enter fullscreen mode Exit fullscreen mode

This variable is being declared as of type EditMode and of value .inactive. If you search up the documentation for @Environment(\.editMode) var editMode, you will find that editMode has 3 enum values, .inactive being one of them.

Now that we have our own state variable, we actually need to inject it into view. Notice the .environment() modifiers?

NavigationStack {
    VStack(spacing: 16) {

        if editMode == .active { // 4) this follows our own state
            Text("I appear only in Edit Mode")
                .padding()
                .background(.yellow)
        }

        Text("Always visible")
        EditButton() // 3) this toggles swiftui's editMode env var, not our own state variable
        Button("tap me for second view") {
            isShowing.toggle()
        }
    }
    .environment(\.editMode, $editMode) // 2) feed and bind your own state into swiftui's env var
    .fullScreenCover(isPresented: $isShowing) {
        SecondContentView()
            .environment(\.editMode, $editMode) // 5) feed and bind your own state into swiftui's env var for the second view
    }
}
Enter fullscreen mode Exit fullscreen mode

.environment(\.editMode, $editMode) means I want to bind the value of my own self-declared editMode into the environment variable binding of \.editMode.

Also, notice how we have to keep injecting our state into our view and onto the child view? That's expected and normal behaviour.

This then translates to our second view:

struct SecondContentView: View {

    // 6) @Environment introduced as a bind here
    @Environment(\.editMode) var editMode

    var body: some View {
        NavigationStack {
            VStack(spacing: 16) {

                if editMode?.wrappedValue.isEditing == true { // 7) this follows the actual environment API
                    Text("I appear only in Edit Mode")
                        .padding()
                        .background(.yellow)
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This code now coincides nicely with how editMode is often portrayed as. Now, it is a binding variable and it has value.

We also have a different if statement now because we are actually using the API now, not our own state variable.

if editMode?.wrappedValue.isEditing == true { // 7) this follows the actual environment API
Enter fullscreen mode Exit fullscreen mode

The code should work now if you run it. The second view will follow if you toggle editMode from your first view.

Top comments (0)