如何在Mac版SwiftUI中实现原生Table列拖拽排序及列标题onDrag?
在Mac SwiftUI中实现原生Table列拖拽排序
核心实现思路
SwiftUI原生Table(macOS 13+)暂无内置列拖拽排序API,需通过维护列顺序状态+自定义拖拽/落地代理实现。以下是完整可运行的代码方案,同时满足你将age列设为初始首位的需求。
完整代码示例
import SwiftUI // 统一管理列的标识与显示标题 enum TableColumn: String, CaseIterable, Identifiable { case userName, age, address var id: String { rawValue } var title: String { switch self { case .userName: return "用户名" case .age: return "年龄" case .address: return "地址" } } } // 示例数据模型 struct User: Identifiable { let id = UUID() let userName: String let age: Int let address: String } struct DraggableTableView: View { // 维护列的显示顺序,初始将age设为首位 @State private var columnOrder: [TableColumn] = [.age, .userName, .address] // 模拟数据源 private let users: [User] = [ User(userName: "张三", age: 25, address: "北京市朝阳区"), User(userName: "李四", age: 30, address: "上海市浦东新区"), User(userName: "王五", age: 28, address: "广州市天河区") ] var body: some View { Table(users) { // 按当前顺序渲染列 ForEach(columnOrder) { column in switch column { case .userName: TableColumn(column.title, value: \.userName) .header { columnHeader(for: column) } case .age: TableColumn(column.title, value: \.age) .header { columnHeader(for: column) } case .address: TableColumn(column.title, value: \.address) .header { columnHeader(for: column) } } } } .frame(minWidth: 600, minHeight: 400) } // 封装带拖拽交互的列标题视图 private func columnHeader(for column: TableColumn) -> some View { Text(column.title) .onDrag { // 将列标识作为拖拽数据传递 NSItemProvider(object: column.rawValue as NSString) } .onDrop(of: [.utf8PlainText], delegate: ColumnDropDelegate(column: column, currentOrder: $columnOrder)) } } // 自定义DropDelegate处理拖拽落地逻辑 class ColumnDropDelegate: DropDelegate { let targetColumn: TableColumn @Binding var columnOrder: [TableColumn] init(column: TableColumn, currentOrder: Binding<[TableColumn]>) { self.targetColumn = column self._columnOrder = currentOrder } func performDrop(info: DropInfo) -> Bool { guard let item = info.itemProviders(for: [.utf8PlainText]).first else { return false } item.loadObject(ofClass: NSString.self) { [weak self] (object, error) in guard let self = self, let rawValue = object as? String, let draggedColumn = TableColumn(rawValue: rawValue), draggedColumn != self.targetColumn else { return } DispatchQueue.main.async { // 调整列顺序:移除拖拽列,插入到目标列位置 self.columnOrder.removeAll { $0 == draggedColumn } if let targetIndex = self.columnOrder.firstIndex(of: self.targetColumn) { self.columnOrder.insert(draggedColumn, at: targetIndex) } } } return true } func proposal(for info: DropInfo) -> DropProposal? { return DropProposal(operation: .move) } } #Preview { DraggableTableView() }
关键细节说明
- 列状态管理:用
TableColumn枚举统一列标识,columnOrder数组控制列的显示顺序,初始值直接设置为[.age, .userName, .address]即可让age列居首。 - 拖拽触发:通过
onDrag将列的rawValue转为NSItemProvider作为拖拽数据。 - 落地处理:自定义
ColumnDropDelegate,在performDrop中解析拖拽数据,调整columnOrder数组的顺序,SwiftUI会自动根据数组更新Table的列布局。 - 线程安全:由于
loadObject是异步回调,需切换到主队列更新UI状态。
内容的提问来源于stack exchange,提问作者leihu huo
相关产品推荐
相关产品推荐

