You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中实现List行拖拽并将条目设为目标行子项的问题

SwiftUI List 行内拖拽触发Drop回调的解决方案

问题根源

  • 修饰符挂载位置错误:你将onDrop、onDrag修饰符挂载在ForEach容器上,而非每个列表行元素上,List默认行交互会拦截拖拽事件,导致行上的Drop无法触发
  • 类型标识符不匹配:onDrag方法中typeIdentifier参数传了空值,和onDrop指定接收的UTType.text类型不对应,系统判定拖拽内容不匹配,不会触发回调
  • 现有DropDelegate未携带目标行信息,无法后续完成「将拖拽条目设为目标行子项」的业务逻辑

修复后完整代码

import SwiftUI
import UniformTypeIdentifiers

struct Item: Identifiable {
    let id = UUID()
    let title: String
    // 后续可新增children属性存储子项
    // var children: [Item] = []
}

struct EditListView: View {
    @State private var items: [Item] = [
        Item(title: "Apple"),
        Item(title: "Banana"),
        Item(title: "Papaya"),
        Item(title: "Mango")
    ]
    
    var body: some View {
        VStack {
            List {
                ForEach(items) { item in
                    Text(item.title)
                        // 修饰符挂载到每个行元素上
                        .onDrag {
                            // 统一用UTType.text作为类型标识,携带拖拽源的id
                            NSItemProvider(item: item.id.uuidString as NSSecureCoding, typeIdentifier: UTType.text.identifier)
                        }
                        .onDrop(of: [UTType.text], delegate: ItemDropDelegate(targetItemId: item.id, items: $items))
                }
            }
            
            Text("Drop Item Here..")
                .fontWeight(.heavy)
                .onDrop(of: [UTType.text], delegate: dropDelegate())
        }
    }
}

// 自定义携带目标行信息的DropDelegate
class ItemDropDelegate: DropDelegate {
    let targetItemId: UUID
    @Binding var items: [Item]
    
    init(targetItemId: UUID, items: Binding<[Item]>) {
        self.targetItemId = targetItemId
        self._items = items
    }
    
    func performDrop(info: DropInfo) -> Bool {
        // 读取拖拽源的id
        guard let itemProvider = info.itemProviders(for: [UTType.text]).first else {
            return false
        }
        
        itemProvider.loadObject(ofClass: NSString.self) { object, error in
            DispatchQueue.main.async {
                if let sourceIdStr = object as? String, let sourceId = UUID(uuidString: sourceIdStr) {
                    print("拖拽成功:源条目ID \(sourceId),目标条目ID \(self.targetItemId)")
                    // 这里可自行实现将源条目添加为目标条目的子项逻辑
                }
            }
        }
        return true
    }
}

// 原来的全局DropDelegate保留即可
class dropDelegate: DropDelegate {
    func performDrop(info: DropInfo) -> Bool {
        print("drop success")
        return true
    }
}

验证说明

修复后拖拽任意列表行到另一行上,控制台会打印对应的源id和目标id,已触发Drop回调。后续你只需要调整Item的数据结构,增加子项存储字段,在performDrop中对应修改数据源即可完成「设置子项」的需求。

内容的提问来源于stack exchange,提问作者unknown

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 20:45:04