Reputation: 499
How can I add drag and drop to reorder rows on SwiftUI? Just a clean solution without 'Edit mode'. Here an example:
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
Reputation: 3706
SwiftUI iOS 16+ solution:
List($fruits, id: \.self, editActions: .move) { $fruit in
//List item UI
Image(fruit.image)
}
.listStyle(.plain)
Upvotes: 3
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
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
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