Matthew Rodríguez
Matthew Rodríguez

Reputation: 499

List with drag and drop to reorder on SwiftUI

How can I add drag and drop to reorder rows on SwiftUI? Just a clean solution without 'Edit mode'. Here an example:

enter image description here

UPDATE 1

I asked this question on The SwiftUI Lab and the author replied with this code. Only works on iPad

import SwiftUI
    
    struct Fruit: Identifiable {
        let id = UUID()
        let name: String
        let image: String
    }
            struct ContentView: View {
            @State var selection: Set<UUID> = []
        
            @State private var fruits = [
                Fruit(name: "Apple", image: "apple"),
                Fruit(name: "Banana", image: "banana"),
                Fruit(name: "Grapes", image: "grapes"),
                Fruit(name: "Peach", image: "peach"),
                Fruit(name: "Kiwi", image: "kiwi"),
            ]
        
            var body: some View {
        
                VStack {
                    NavigationView {
                        List(selection: $selection) {
                            ForEach(fruits) { fruit in
                                HStack {
                                    Image(fruit.image)
                                        .resizable()
                                        .frame(width: 30, height: 30)
        
                                    Text(fruit.name)
                                }
                            }
                            .onMove { _, _ in }
                        }
                        .navigationBarTitle("Fruits (Top)")
                    }
                }
            }
        }

UPDATE 2

import SwiftUI
    
struct ContentView: View {
    @State private var fruits = ["Apple", "Banana", "Orange", "Strawberry", "Grape"]
    
    var body: some View {
        NavigationStack {
            List($fruits, id: \.self, editActions: [.delete, .move]) { $fruit in
                Text(fruit)
            }
            .listStyle(.plain)
        }
    }
}

credits: Teja Kumar Bethina

Upvotes: 26

Views: 15072

Answers (4)

Teja Kumar Bethina
Teja Kumar Bethina

Reputation: 3706

SwiftUI iOS 16+ solution:

List($fruits, id: \.self, editActions: .move) { $fruit in 
  //List item UI 
  Image(fruit.image)
}
.listStyle(.plain)

Upvotes: 3

Roman Predein
Roman Predein

Reputation: 211

Draggable items in SwiftUI List with changing the order

I solved it with return NSItemProvider() when I try to drag an item. And standard .onMove function.

If I understand correctly, I am grabbing a row container as an NSObject (thanks to initialization NSItemProvider), and .OnMove allows me the ability to reorder items in the List.

I'm still learning and may misunderstand some of the nuances. There must be a better explanation. But it definitely works (I only tested this on ios 15 because I use the .background property in my project).

// View
List {
    ForEach(tasks) { task in
        HStack { // Container of a row
             NavigationLink {
                 Text("There will be an editing View")
             } label: {
                 TaskListRowView(task: task)
             }
        }
        .onDrag { // mean drag a row container
             return NSItemProvider()
        }
     }
     .onDelete(perform: deleteItems)
     .onMove(perform: move) 
}

// Function
func move(from source: IndexSet, to destination: Int) {
    tasks.move(fromOffsets: source, toOffset: destination )
}

Upvotes: 21

codingFriend1
codingFriend1

Reputation: 6807

To put the list in the edit mode when the user long presses an item, you can use a state flag and set the edit environment value accordingly. It is important to make the flag changes animated in order not to look very weird.

struct ContentView: View {
    @State private var fruits = ["Apple", "Banana", "Mango"]
    @State private var isEditable = false

    var body: some View {
        List {
            ForEach(fruits, id: \.self) { user in
                Text(user)
            }
            .onMove(perform: move)
            .onLongPressGesture {
                withAnimation {
                    self.isEditable = true
                }
            }
        }
        .environment(\.editMode, isEditable ? .constant(.active) : .constant(.inactive))
    }

    func move(from source: IndexSet, to destination: Int) {
        fruits.move(fromOffsets: source, toOffset: destination)
        withAnimation {
            isEditable = false
        }
    }
}

Upvotes: 18

PaulWoodIII
PaulWoodIII

Reputation: 2656

There is a great writeup on this at: https://www.hackingwithswift.com/quick-start/swiftui/how-to-let-users-move-rows-in-a-list

I don't want to copy paste Paul's code directly here so here's another example I wrote myself with a slightly different structure.

import SwiftUI

struct ReorderingView: View {

  @State private var items = Array((0...10))

  var body: some View {
    NavigationView {
      VStack {
        List {
          ForEach(items) { item in
              Text("\(item)")
          }.onMove { (source: IndexSet, destination: Int) -> Void in
            self.items.move(fromOffsets: source, toOffset: destination)
          }
        }
      }
      //.environment(\.editMode, .constant(.active)) // use this to keep it editable always
      .navigationBarItems(trailing: EditButton()) // can replace with the above
      .navigationBarTitle("Reorder")
    }
  }
}

struct ReorderingView_Previews: PreviewProvider {
  static var previews: some View {
    ReorderingView()
  }
}

extension Int: Identifiable { // Please don't use this in production
  public var id: Int { return self }
}

Upvotes: -4

Related Questions