使用 onDrag、onDrop 和 onMove 在 SwiftUI 列表中组合项目替换和重新排序

问题描述 投票:0回答:1

我正在开发一个 SwiftUI 项目,其中有一个使用列表视图显示的项目列表。我想提供两个功能:列表中的项目替换和项目重新排序。

  1. 项目替换:我希望能够从列表中拖动一个项目并将其放到另一个项目上以替换它。放下时,原始项目应替换为拖动的项目。
  2. 项目重新排序:我还想启用使用拖放手势对列表中的项目重新排序的功能。 我尝试过单独使用 onDrag 和 onDrop 修饰符,但未能成功组合这两个功能。当我使用 onMove 进行重新排序时,它会覆盖 onDrag 行为。

我正在寻求社区关于如何有效结合这两种功能的意见。如何同时使用 onDrag 和 onDrop 修饰符在列表中启用项目替换和重新排序?

任何见解、建议或代码示例将不胜感激。

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

swiftui drag-and-drop reorderlist
1个回答
0
投票

不要使用 .onDrag,而是使用 .itemProvider。 经过数小时的尝试和错误后,这似乎就是魔法。

© www.soinside.com 2019 - 2024. All rights reserved.