Combining item replacement and reordering in SwiftUI List using onDrag and onDrop and onMove

123 Views Asked by At

I'm working on a SwiftUI project where I have a list of items displayed using the List view. I want to provide two functionalities: item replacement and item reordering within the list.

  1. Item Replacement: I want to be able to drag an item from the list and drop it onto another item to replace it. When dropped, the original item should be replaced with the dragged item.
  2. Item Reordering: I also want to enable the ability to reorder items within the list using drag and drop gestures. I have tried using the onDrag and onDrop modifiers individually, but I haven't been able to combine both functionalities successfully. When I use onMove for reordering, it overrides the onDrag behavior.

I'm seeking community opinions on how to combine these two functionalities effectively. How can I enable both item replacement and reordering within the list using the onDrag and onDrop modifiers simultaneously?

Any insights, suggestions, or code examples would be greatly appreciated.

import SwiftUI

struct ContentView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4"]
    @State private var highlightedIndex: Int?
    
    var body: some View {
        List {
            ForEach(items.indices, id: \.self) { index in
                let item = items[index]
                HStack {
                    Text(item)
                        .foregroundColor(highlightedIndex == index ? .red : .primary)
                    Spacer()
                }
                .onDrag {
                    NSItemProvider(object: NSString(string: "\(index)"))
                }
                .onDrop(of: [.text], delegate: MyDropDelegate(itemIndex: index, items: $items, highlightedIndex: $highlightedIndex))
            }
            .onMoveCommand(perform: moveItem)
        }
    }
    
    func moveItem(_ direction: MoveCommandDirection) {
        guard let sourceIndex = items.firstIndex(where: { $0 == items[highlightedIndex ?? 0] }) else {
            return
        }
        
        var destinationIndex: Int
        
        switch direction {
        case .up:
            destinationIndex = sourceIndex - 1
            if destinationIndex < 0 {
                destinationIndex = items.count - 1
            }
        case .down:
            destinationIndex = sourceIndex + 1
            if destinationIndex >= items.count {
                destinationIndex = 0
            }
        @unknown default:
            return
        }
        
        items.move(fromOffsets: IndexSet(integer: sourceIndex), toOffset: destinationIndex)
        highlightedIndex = destinationIndex
    }
}

struct MyDropDelegate: DropDelegate {
    let itemIndex: Int
    @Binding var items: [String]
    @Binding var highlightedIndex: Int?
    
    func performDrop(info: DropInfo) -> Bool {
        let item = info.itemProviders(for: ["public.text"]).first
        item?.loadObject(ofClass: NSString.self) { item, _ in
            if let itemString = item as? NSString {
                let intValue = itemString.intValue
                let swiftInt = Int(intValue)
                
                let sourceIndex = swiftInt
                let droppedItem = items[sourceIndex]
                if let highlightedIndex = highlightedIndex {
                    items[highlightedIndex] = droppedItem
                }
            }
        }
        return true
    }
    
    func validateDrop(info: DropInfo) -> Bool {
        highlightedIndex = itemIndex
        return info.hasItemsConforming(to: [.text])
    }
    
    func dropEntered(info: DropInfo) {
        highlightedIndex = itemIndex
    }
    
    func dropExited(info: DropInfo) {
        highlightedIndex = nil
    }
}

enter image description here

enter image description here

0

There are 0 best solutions below