2

I'm developing an app, one view of which has the primary goal of allowing users to reorder a List of NavigationLinks, but which I would also like to allow navigation & a few other things. I want users to be able to:

  1. Reorder by dragging on the reorder control.
  2. Navigate to the link by tapping elsewhere on the row.
  3. Swipe from the leading edge to activate a swipe action.

At the moment, enabling EditMode (to allow reordering) disables navigation & swipe actions; I haven't been able to find a workaround that allows all 3 functions simultaneously. Is there a good way to do this?

Here's an example:

struct ReorderableListView: View {
    var items: [X] // List of custom objects
    var body: some View {
       NavigationView {
           List {
               ForEach(items) { item in
                   NavigationLink(destination: CustomView(item)) { // winds up disabled by edit mode
                      Text(item.name)
                   }
                   .swipeActions(edge: .leading) { Button("Swipe") {print("swipe")} } // winds up disabled by edit mode
               }
               .onMove { from, to in
                   print("move \(from)  to \(to)")
               }
           }
           .environment(\.editMode, .constant(.active)) // always in edit mode for reordering
       }
    }
}

Update:

Based on the accepted answer, it looks like this behavior got updated in iOS 16: Now, if onMove is implemented, you can reorder with a long press even when not in edit mode, which allows these three actions to coexist. I've added custom drag-handles to make this behavior obvious to the user, but otherwise I'm going with exactly the solution given in the accepted answer, below.

lelandpaul
  • 85
  • 5

1 Answers1

2

I am not sure that the .environment is necessary in this case (I could be wrong). You can remove that piece.

Additionally, you should add an ID to each item in your foreach. This should ideally come from your model when you create new items (for example, your model can contain an ID variable = UUID()), but for the time being we can add it inline in your foreach.

I had to write some code on my end to get this up and running, so my solution is based on the code I spun up (very similar to yours, but missing your custom items object):

    struct ReorderableListView: View {
    var items: [X] // List of custom objects
    var body: some View {
       NavigationView {
           List {
                    // added ID here
               ForEach(items, id: \.self) { item in
                   NavigationLink(destination: CustomView(item)) { // winds up disabled by edit mode
                      Text(item.name)
                   }
                   .swipeActions(edge: .leading) { Button("Swipe") {print("swipe")} } // winds up disabled by edit mode
               }
               .onMove { from, to in
                   print("move \(from)  to \(to)")
               }
           }
          // REMOVED .environment(\.editMode, .constant(.active)) // always in edit mode for reordering
       }
    }
}

For reference, here is the code I wrote locally to fill in the gaps. This is what worked & what I implemented within your code:

    struct ReorderableListView: View {
    @State var items = [1, 2, 3] // List of custom objects
    var body: some View {
        NavigationView {
            List {
                ForEach(items, id: \.self) { item in
                    NavigationLink(destination: ContentView2()) { // winds up disabled by edit mode
                        Text("\(item)")
                    }
                    .swipeActions(edge: .leading) { Button("Swipe") {print("swipe")} } // winds up disabled by edit mode
                }
                .onMove { from, to in
                    print("move \(from)  to \(to)")
                }
            }
         //   .environment(\.editMode, .constant(.active)) // always in edit mode for reordering
        }
    }
}
nickreps
  • 903
  • 8
  • 20
  • Thanks for the response! Your code definitely handles the swipe actions and navigation, but I don't see how it handles reordering. The `onMove` is there, but the drag handles that allow reordering are not visible/draggable if the list isn't in EditMode, so that will never get called. (That's what the `.environment` line is for in my original code — to keep edit mode permanently active — but as mentioned that has unfortunate side effects.) – lelandpaul Sep 29 '22 at 01:33
  • Oh I see, you want those indicators there. I am still able to drag and move rows around without the environment piece (so partially solves the issue?) - just need to long hold on a row and you can reorder. – nickreps Sep 29 '22 at 01:35
  • Ideally I would like to have the indicators, but I'd settle for just long-press-&-drag to reorder! But at least on my end, your version is not reorderable — long hold does nothing. What version of xcode are you running? – lelandpaul Sep 29 '22 at 01:41
  • iOS 16/Xcode 14 - iPhone 14 pro simulator. – nickreps Sep 29 '22 at 01:43
  • gah, if I've wasted 3 days on this just because my version of xcode is slightly out of date I'm going to scream :/ – lelandpaul Sep 29 '22 at 01:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/248429/discussion-between-nickreps-and-lelandpaul). – nickreps Sep 29 '22 at 01:57
  • Just to add that you can only use onMove when you are in editMode. – Ptit Xav Sep 29 '22 at 13:33