I have the following situation: in SwiftUI you can use List
‘s onMove
and onDelete
to reorder and delete items of an array. Swift even provides an EditButton
which will force the environment editMode
so you can move and delete items. There are 2 ways for you to move items, while having onMove
on onDelete
:
- Hold to move and swipe to delete
- Tap on edit mode and use the red dot to delete or the triple lines to drag and reorder.
I want to be able to use only number 2 while number 1 being deactivated so I tried the following:
struct CustomListData: Identifiable {
let id = UUID()
let title: String
}
struct ContentView: View {
@State var customListData = [
CustomListData(title: "One"),
CustomListData(title: "Two"),
CustomListData(title: "Three"),
CustomListData(title: "Four"),
CustomListData(title: "Five")
]
@Environment(.editMode) var editMode
var inEditMode: Bool {
editMode?.wrappedValue.isEditing ?? false
}
var body: some View {
VStack {
List {
ForEach(customListData) { item in
Text(item.title)
}
.onDelete(perform: inEditMode ? deleteItems : nil)
.onMove(perform: inEditMode ? moveItems : nil)
}
EditButton()
}
}
func deleteItems(at offsets: IndexSet) {
customListData.remove(atOffsets: offsets)
}
func moveItems(fromIndex: IndexSet, newIndex: Int) {
customListData.move(fromOffsets: fromIndex, toOffset: newIndex)
}
}
The problem is that when edit button is tapped I don’t go into edit mode aka not seeing the red deletion circle on the left and triple lines on the right. Instead I am just able to move them in behaviour 1. For more context this is what I mean https://imgur.com/ZsGFgJ8
2
Answers
Unfortunately, as of iOS 17, SwiftUI’s
EditButton
is still buggy and is nowhere near a parity of the UIKit’sUITableView
delegate methods functionalities.In terms of your question, it is partially due to the buggy editing behaviors. Consider the following situations:
Then both 1 and 2 would work, which is how SwiftUI expects you to use the
EditButton
.Use your current version. When the edit button is tapped, try move or delete a row. You’ll see the buggy behavior that when you move a row, the row will show both indicators!
Because of the buggy behavior of
EditButton
, there isn’t an elegant way to make it work as you described 😩. There are 2 alternatives that are worth considering:.id(inEditMode)
to each list row or the entire list, to force the view to recompute when the edit mode changes.EditButton
, create custom UIs to mimic the buttons in the row. Therefore, you have full control over the editing states.The undocumented trick with
@Environment(.editMode)
is it needs to be in a customView
that is inside a container likeList
,Form
,VStack
etc. or inside a modifier applied to one of those containers like.toolbar
. The reason is it is theList
that sets theeditMode
binding in the@Environment
. So if you try to access that in aView
higher in the hierarchy then there is no valideditMode
binding which is why attempting to read its wrapped value is alwaysfalse
. Restructuring it to something like this should fix the problem: