SwiftUI多视图间多项目拖拽及分区内重排方案咨询
SwiftUI实现跨视图多项目拖拽+分区内重排方案
核心思路
- 维护跨视图共享的选中项目集合,标记需要拖拽的项
- 自定义拖拽数据结构,将所有选中项打包后通过单个
NSItemProvider传递 - 实现自定义
DropDelegate处理跨视图接收与分区内重排逻辑 - 结合
LazyVStack和onMove保留分区内的重排能力
具体实现步骤
1. 定义数据模型与选中状态管理
先实现可序列化的数据模型,以及跨视图共享的选中状态管理器:
import SwiftUI struct Item: Identifiable, Codable { let id: UUID let title: String let sectionId: String // 标记所属分区 } class DragSelectionManager: ObservableObject { @Published var selectedItemIds: Set<UUID> = [] func toggleSelection(for item: Item) { selectedItemIds.contains(item.id) ? selectedItemIds.remove(item.id) : selectedItemIds.insert(item.id) } func clearSelection() { selectedItemIds.removeAll() } }
2. 实现可多选拖拽的项目视图
在项目视图中绑定选中状态,拖拽时打包所有选中项的数据:
struct DraggableItemView: View { let item: Item @ObservedObject var selectionManager: DragSelectionManager @Binding var items: [Item] var body: some View { HStack { Text(item.title) .padding() .background(selectionManager.selectedItemIds.contains(item.id) ? Color.blue.opacity(0.3) : Color.clear) .cornerRadius(8) Spacer() if selectionManager.selectedItemIds.contains(item.id) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.blue) } } .onTapGesture { selectionManager.toggleSelection(for: item) } .onDrag { let selectedItems = items.filter { selectionManager.selectedItemIds.contains($0.id) } guard let data = try? JSONEncoder().encode(selectedItems) else { return NSItemProvider() } return NSItemProvider(object: data as NSData) } .onDrop(of: [.data], delegate: ItemDropDelegate( item: item, items: $items, selectionManager: selectionManager )) } }
3. 自定义DropDelegate处理接收逻辑
实现DropDelegate来处理跨视图拖拽接收和分区内重排:
class ItemDropDelegate: DropDelegate { let item: Item @Binding var items: [Item] let selectionManager: DragSelectionManager init(item: Item, items: Binding<[Item]>, selectionManager: DragSelectionManager) { self.item = item self._items = items self.selectionManager = selectionManager } func performDrop(info: DropInfo) -> Bool { guard let provider = info.itemProviders(for: [.data]).first else { return false } provider.loadObject(ofClass: NSData.self) { data, _ in guard let data = data as? Data, let droppedItems = try? JSONDecoder().decode([Item].self, from: data) else { return } DispatchQueue.main.async { // 移除当前分区内的选中项 let filteredItems = self.items.filter { !self.selectionManager.selectedItemIds.contains($0.id) } let insertIndex = self.items.firstIndex(of: self.item) ?? filteredItems.count // 插入拖拽过来的项 self.items = Array(filteredItems[0..<insertIndex]) + droppedItems + Array(filteredItems[insertIndex...]) self.selectionManager.clearSelection() } } return true } func proposal(for info: DropInfo) -> DropProposal? { return DropProposal(operation: .move) } }
4. 构建分区视图与跨视图布局
用LazyVStack构建分区,让两个视图共享同一个选中状态管理器:
struct ContentView: View { @StateObject private var selectionManager = DragSelectionManager() @State private var sectionA: [Item] = [ Item(id: UUID(), title: "A1", sectionId: "A"), Item(id: UUID(), title: "A2", sectionId: "A") ] @State private var sectionB: [Item] = [ Item(id: UUID(), title: "B1", sectionId: "B"), Item(id: UUID(), title: "B2", sectionId: "B") ] @Environment(\.editMode) private var editMode var body: some View { VStack(spacing: 20) { Button(editMode?.wrappedValue == .active ? "完成" : "编辑") { withAnimation { editMode?.wrappedValue = editMode?.wrappedValue == .active ? .inactive : .active } } SectionView(title: "分区A", items: $sectionA, selectionManager: selectionManager) SectionView(title: "分区B", items: $sectionB, selectionManager: selectionManager) } .padding() .environment(\.editMode, $editMode) } } struct SectionView: View { let title: String @Binding var items: [Item] @ObservedObject var selectionManager: DragSelectionManager var body: some View { VStack(alignment: .leading) { Text(title) .font(.headline) .padding(.bottom, 8) LazyVStack(spacing: 8) { ForEach(items) { item in DraggableItemView(item: item, selectionManager: selectionManager, items: $items) } .onMove(perform: moveItems) } .padding(.leading) } } func moveItems(from source: IndexSet, to destination: Int) { items.move(fromOffsets: source, toOffset: destination) } }
关键注意点
- 跨视图拖拽时,若两个分区数据源独立,需在
DropDelegate中同步更新原分区的数组(可通过全局数据源或通知实现) - 选中状态的视觉反馈需清晰,让用户明确知晓拖拽范围
- 可通过
preview修饰符自定义拖拽预览视图,优化用户体验 LazyVStack适合长列表场景,能保证拖拽时的性能
内容的提问来源于stack exchange,提问作者Anastasia Veremiichyk
相关产品推荐
相关产品推荐

