SwiftUI EditButton() bug

Viewed 1187

I'm presenting a List inside a modal. If I'm inside a NavigationView the EditButton its totally broken.

enter image description here

struct ContentView: View {
@State var showSheetView = false

var body: some View {
    NavigationView {
        Button(action: {
            self.showSheetView.toggle()
        }) {
            Image(systemName: "bell.circle.fill")
                .font(Font.system(.title))
        }
        .sheet(isPresented: $showSheetView) {
            SheetView()
        }
    }
}
}

struct SheetView: View {
@State private var myArray: [String] = ["One", "Two", "Three"]
var body: some View {
    NavigationView {
        VStack {
            List {
                ForEach(myArray, id: \.self) { item in
                    Text(item)
                }.onDelete(perform: { indexSet in
                })
            }
        }
        .navigationBarItems(trailing: EditButton())
    }
}
}

If I remove the NavigationView where i present from, then at first it seems to work, the second time i present it gets broken again.

struct ContentView: View {
@State var showSheetView = false

var body: some View {
        Button(action: {
            self.showSheetView.toggle()
        }) {
            Image(systemName: "bell.circle.fill")
                .font(Font.system(.title))
        }
        .sheet(isPresented: $showSheetView) {
            SheetView()
        }
}
}

enter image description here

3 Answers

Manually handling the editMode works for me on macOS Big Sur with Xcode 12.1 / iOS 14.1.

I also had a problem of EditButton showing "Edit" again in an edit mode when I rotate the simulator, and the below solution solves it as well.

The following solution uses a custom EditButton struct that handles a manual editMode binding. First the custom EditButton:

struct EditButton: View {
    @Binding var editMode: EditMode

    var body: some View {
        Button {
            switch editMode {
            case .active: editMode = .inactive
            case .inactive: editMode = .active
            default: break
            }
        } label: {
            if let isEditing = editMode.isEditing, isEditing {
                Text("Done")
            } else {
                Text("Edit")
            }
        }
    }
}

Using the above EditButton is straightforward:

struct SheetView: View {
    @State private var myArray: [String] = ["One", "Two", "Three"]
    @State private var editMode = EditMode.inactive

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(myArray, id: \.self) { item in
                        Text(item)
                    }.onDelete(perform: { indexSet in
                    })
                }
            }
            .navigationBarItems(trailing: EditButton(editMode: $editMode))
            .environment(\.editMode, $editMode)
            .animation(.spring(response: 0))
        }
    }
}

The EditButton in the trailing navigation bar item handles the @State private var editMode kept in SheetView. This editMode is then injected into the inner views using the environment .environment(\.editMode, $editMode). For the animation effect of the edit mode transition, I found .spring(response: 0) most appropriate.

Instead of

.navigationBarItems(trailing: EditButton())

you could try:

.toolbar { EditButton() }

I had the same problem and this worked fine for me.

This worked for me, in iOS 16.0

struct MyEditButton: View {
  
  @Binding var editMode: EditMode
  
  var body: some View {
    Button {
      switch editMode {
      case .active: editMode = .inactive
      case .inactive: editMode = .active
      default: break
      }
    } label: {
      if let isEditing = editMode.isEditing, isEditing {
        Text("Done")
      } else {
        Text("Edit")
      }
    }
  }
}

struct ContentView: View {
  
  @State var editMode: EditMode = .inactive
  
  @State var list = ["A", "B", "C"]
  
  var body: some View {
    Form {
      Section(header: HStack {
        MyEditButton(editMode: $editMode)
          .frame(maxWidth: .infinity, alignment: .trailing)
          .overlay(Text("LIST"), alignment: .leading)
      }) {
        List {
          ForEach(list, id: \.self) { value in
            Text(value)
          }
          .onDelete(perform: deleteSection)
          .onMove(perform: moveSection)
        }
      }
    }
    .environment(\.editMode, $editMode)
    .padding()
  }
  
  func deleteSection(at offsets: IndexSet) {
    list.remove(atOffsets: offsets)
  }
  
  func moveSection(from source: IndexSet, to destination: Int) {
    list.move(fromOffsets: source, toOffset: destination)
  }
}

enter image description here

Related