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
相关产品推荐
相关产品推荐

