EditMode & EditButton not working in a way I expect

I have something that looks like:

        NavigationStack {
            List(self.items, id: \.self, selection: self.$selectedItems) { item in
                NavigationLink {
                    ItemView(item: item)
                        .environment(\.managedObjectContext, self.viewContext)
                } label: {
                    LabelWithMenuView(object: item) { ptr in
                        self.labelHandler(item: item, newName: ptr)
                    }
                }
            }

            if self.editMode?.wrappedValue == .active {
                editButtons
            } else {
                TextField("Add Item", text: self.$newItem)
                    .onSubmit {
                        self.addItem()
                        self.newItem = ""
                    }
                    .padding()
            }
        }
#if os(iOS)
        .toolbar {
            EditButton()
        }
        .onChange(of: self.editMode?.wrappedValue) { old, new in
            print("editMode \(old) -> \(new)")
        }
#endif

With that layout, the edit button doesn't show up at all; if I put it as part of the List, it does show up, but the first click doesn't do anything; after that, it works, but the onChange handler doesn't show it getting changed, and the editButtons don't go away.

Post not yet marked as solved Up vote post of kithrup Down vote post of kithrup
621 views

Replies

Thanks for bringing that to attention @kithrup , please file a feedback report at https://feedbackassistant.apple.com and post the FB number here. As a workaround for now, you can create a button and manipulate the environment value yourself, like this:

struct ContentView: View {
    @Environment(\.editMode) private var editMode

    var body: some View {
        NavigationStack {
            List(0..<10, id: \.self) { item in
                NavigationLink {
                    Text("item")
                } label: {
                   Text("label")
                }
            }
            .toolbar {
                Button("Edit") {
                    editMode?.wrappedValue = .active
                }
            }
            if self.editMode?.wrappedValue == .active {
                Text("edit")
            } else {
                Text("not editing")
            }
               
        }
        .onChange(of: self.editMode?.wrappedValue) { old, new in
            print("editMode \(old) -> \(new)")
        }

    }
}

You'll now see the onChange get called, although you'll need to change the button title to "Done" and switch the value back to .inactive after tapping it again.

I tried that, it did not solve the problem. There are, in fact, two: • If the view with the EditButton does not have a parent view that is a NavigationStack (or perhaps types of views), there is no toolbar, and thus there is no edit button. (That is, if ContentView.swift does not contain a NavigationStack, then there is no toolbar.) • If I put a NavigationStack in ContentView, then there is a toolbar and an Edit button, but edit mode is not enabled the first time I click on the Edit button

If I add

        .toolbar {
            EditButton()
        }
        .onChange(of: self.editMode?.wrappedValue) { o, v in
            print("editMode \(o) -> \(v)")
        }

then when I click on the button the first time, it prints

editMode Optional(SwiftUI.EditMode.inactive) -> Optional(SwiftUI.EditMode.active)

and... never again prints anything, although edit mode clearly does change.

  • yes, you do need a NavigationStackare both the toolbar and the onChange within the NavigationStack? When reproducing this I do not need to click twice, although I get no logs in my console.

    In reference to my above post, it would not automatically start editing, you would need to handle each case specifically

  • There is a NavigationStack in the view -- but it has to be wrapped inside of another one?. The onChange should be within the NavigationStack?

Add a Comment

I filed FB13428947 and attached a simple project.

  • Thanks for filing that!

Add a Comment

Ok, I put the .toolbar section on the List, and... it didn't really change behaviour. Then I removed the outer view's NavigationStack and it behaved more strangely -- namely, on the first click, the list items moved very briefly.

However, this only seems to happen the first time it's run on the simulator after Xcode installs it.

I updated my FB and added two screen recordings.

And if I change the .toolbar code to

#if os(iOS)
            .toolbar {
                if self.editMode?.wrappedValue == .active {
                    Button("Done") {
                        self.editMode?.wrappedValue = .inactive
                    }
                } else {
                    Button("Edit") {
                        self.editMode?.wrappedValue = .active
                    }
                }
            }
#endif

when I click on "Edit", the selection buttons appear and immediately disappear.

We also have this issue. Feedback submitted some time ago under FB13201571.

Editing works, as long as NavigationStack is not used. This works.

struct ListSelectionTest: View {
  @State var selection: Set<String> = []
  let strings = ["One", "Two", "Three"]
  var body: some View {
    VStack {
      EditButton()
      List(selection: $selection) {
        ForEach(strings, id: \.self) { string in
          Text(string).tag(string)
        }
      }
    }
  }
}

The problem appears when the List is wrapped in NavigationStack. This only works when the EditButton is tapped TWICE.

struct ListSelectionTest: View {
  @State var selection: Set<String> = []
  let strings = ["One", "Two", "Three"]
  var body: some View {
    NavigationStack {
      VStack {
        EditButton()
        List(selection: $selection) {
          ForEach(strings, id: \.self) { string in
            Text(string).tag(string)
          }
        }
      }
    }
  }
}

Replacing EditButton with a custom Button produces a different visual error. Editing is set correctly, but the multi-selection indicators quickly appear and then disappear again.

struct ListSelectionTest: View {
  @Environment(\.editMode) private var editMode
  @State var selection: Set<String> = []
  let strings = ["One", "Two", "Three"]
  var body: some View {
    NavigationStack {
      VStack {
        Button((editMode?.wrappedValue.isEditing ?? false) ? "Done" : "Edit") {
          editMode?.wrappedValue = (editMode?.wrappedValue.isEditing ?? false) ? .inactive : .active
        }
        List(selection: $selection) {
          ForEach(strings, id: \.self) { string in
            Text(string).tag(string)
          }
        }
      }
    }
  }
}

In short, we suspect there is a SwiftUI bug related to triggering List editing when the list is in NavigationStack.

  • Wow, that's very detailed, and helpful.

Add a Comment