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

SwiftUI List多选移动重排序:Mac正常iOS无法落放问题求助

SwiftUI列表多选后批量拖拽重排序的iOS适配问题

问题描述

我想在SwiftUI的ForEach列表中实现多选+批量拖拽重排序功能,当前代码在Mac上可以正常运行:进入编辑模式后能多选多个项,并且可以将选中的所有项一起拖拽到列表其他位置。但在iOS系统中,只能单独拖拽移动单个项——虽然能进入编辑模式选中多个项,却无法将选中项拖拽后落放到目标位置。

我最初的实现代码:

struct ExampleListView: View {
    @State var items = ["Dave", "Tom", "Jeremy", "Luke", "Phil"]
    
    @State var selectedItems: Set<String> = .init()
    
    var body: some View {
        NavigationView {
            List(selection: $selectedItems) {
                ForEach(items, id: \.self) { item in
                    Text(item).tag(item)
                }.onMove(perform: move)
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
    
    func move(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

之后我尝试了直接绑定数组的List写法,问题依然存在:

struct ExampleListView: View {
    @State var items = ["Dave", "Tom", "Jeremy", "Luke", "Phil"]
    
    @State var selectedItems: Set<String> = .init()
    
    var body: some View {
        NavigationView {
            List($items, id: \.self, editActions: .all, selection: $selectedItems) { $item in
                Text(item).tag(item)
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
    
    func move(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

解决方案

iOS系统的SwiftUI List默认不支持多选后的批量拖拽重排序,需要通过自定义拖拽源和落放代理来实现。以下是完整的适配代码:

struct ExampleListView: View {
    @State var items = ["Dave", "Tom", "Jeremy", "Luke", "Phil"]
    @State var selectedItems: Set<String> = .init()
    
    var body: some View {
        NavigationStack { // iOS 16+ 使用,兼容iOS 15及以下可换回NavigationView
            List(selection: $selectedItems) {
                ForEach(items, id: \.self) { item in
                    Text(item)
                        .tag(item)
                        // 自定义拖拽源:传递选中项集合(无选中则传递当前项)
                        .onDrag {
                            let dragContent = !selectedItems.isEmpty ? selectedItems : [item]
                            return NSItemProvider(object: dragContent.joined(separator: ","))
                        }
                        // 自定义落放代理处理批量移动逻辑
                        .onDrop(of: [.text], delegate: DropHandler(
                            items: $items,
                            selectedItems: $selectedItems,
                            targetItem: item
                        ))
                }
                .onDelete(perform: delete) // 保留原生删除功能
            }
            .navigationTitle("联系人")
            .toolbar {
                EditButton()
            }
        }
    }
    
    // 批量移动选中项到目标位置
    private func moveSelected(to targetIndex: Int) {
        guard !selectedItems.isEmpty else { return }
        
        // 获取所有选中项的索引
        let selectedIndices = items.indices.filter { selectedItems.contains(items[$0]) }
        // 修正目标索引:如果选中项在目标位置之前,需要调整偏移量
        var adjustedTarget = targetIndex
        selectedIndices.forEach {
            if $0 < targetIndex { adjustedTarget -= 1 }
        }
        
        items.move(fromOffsets: IndexSet(selectedIndices), toOffset: adjustedTarget)
        selectedItems.removeAll() // 移动完成后清空选中状态
    }
    
    // 删除选中项
    private func delete(at offsets: IndexSet) {
        let removedItems = items.filter { offsets.contains(items.firstIndex(of: $0)!) }
        items.remove(atOffsets: offsets)
        selectedItems.subtract(removedItems)
    }
    
    // 自定义落放代理
    private struct DropHandler: SwiftUI.DropDelegate {
        @Binding var items: [String]
        @Binding var selectedItems: Set<String>
        let targetItem: String
        
        func performDrop(info: DropInfo) -> Bool {
            guard let provider = info.itemProviders(for: [.text]).first else { return false }
            
            provider.loadObject(ofClass: NSString.self) { object, _ in
                guard let data = object as? String else { return }
                let draggedItems = Set(data.components(separatedBy: ","))
                
                DispatchQueue.main.async {
                    guard let targetIndex = items.firstIndex(of: targetItem) else { return }
                    
                    // 如果拖拽的是选中项集合,执行批量移动
                    if draggedItems == selectedItems {
                        let selectedIndices = items.indices.filter { selectedItems.contains(items[$0]) }
                        var adjustedTarget = targetIndex
                        selectedIndices.forEach {
                            if $0 < targetIndex { adjustedTarget -= 1 }
                        }
                        items.move(fromOffsets: IndexSet(selectedIndices), toOffset: adjustedTarget)
                        selectedItems.removeAll()
                    } else {
                        // 单个项拖拽的默认逻辑
                        if let draggedItem = draggedItems.first, let sourceIndex = items.firstIndex(of: draggedItem) {
                            items.move(fromOffsets: IndexSet(integer: sourceIndex), toOffset: targetIndex)
                        }
                    }
                }
            }
            return true
        }
    }
}

关键说明

  • 兼容iOS 16+,如需适配iOS 15及以下,将NavigationStack替换为NavigationView即可
  • 同时支持单个项拖拽和多选后批量拖拽两种模式
  • 批量移动完成后自动清空选中状态,避免后续操作冲突
  • 保留了原生List的编辑模式和删除功能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:35:00