List|使用 .onMove 添加拖拽排序功能

学习如何在 SwiftUI 中使用 .onMove 修饰器实现 List 列表项的拖拽重排功能。

List|使用 .onMove 添加拖拽排序功能

.onMove 是 SwiftUI 在 iOS 13 引入的修饰器,专门用于 List 和 ForEach 中的项目重排。

它是 UIKit 中 UITableView 的 .moveRowAt 方法的 SwiftUI 版本。

示例项目

苹果提供了示例项目,演示了如何使用 .onMove、.onDelete 以及 iOS 16 上新增的 .draggable。

Adopting drag and drop using SwiftUI | Apple Developer Documentation
Enable drag-and-drop interactions in lists, tables and custom views.

优势与限制

使用 .onMove 修饰符实现原生的 List 列表项重排:

  • 只需要一行代码即可实现
  • 自动处理 UI 反馈(拖动手柄、动画等)

注意事项

  • 在 iOS 上,必须配合 EditButton() 提供编辑模式,才能显示拖动手柄。
  • 专门用于列表内部重排,只能在同一个 List/ForEach 内移动。

平台差异

  • iOS:需要进入编辑模式才能拖动,需要使用 EditButton。
  • macOS:可以直接拖动。

用法

在 ForEach 上使用

ForEach(items) { item in
    // 视图内容
}
.onMove { fromOffsets, toOffset in
    items.move(fromOffsets: fromOffsets, toOffset: toOffset)
}
  • fromOffsets: IndexSet - 被移动项目的原始索引集合
  • toOffset: Int - 目标位置的索引

创建 move() 方法

以下是 move 方法示例:

先通过 var pages = project.displayPages 获取数组副本。

然后,核心是使用 Swift 内置的数组移动方法 —— .move()

.move(fromOffsets: fromOffsets, toOffset: toOffset)
  • fromOffsets 是要移动的页面索引集合(可能多个)
  • toOffset 是目标位置

最后,更新排序值并保存到数据库。

  • 遍历重新排序后的数组
  • 将每个页面的 sortOrder 设置为它在数组中的新位置(0, 1, 2...)

持久化顺序值

在 SwiftData 中,可以添加 sortOrder 变量,用于存储 Page 的顺序:

 var sortOrder: Int?  // 排序顺序

避免使用 onTapGesture

避免在 List 的 Item 上使用 onTapGesture 修饰器,他会导致 .onMove 方法无法正常生效:

优先使用 List 的 selection 机制

将点击选择功能改为使用 List 原生的 selection 机制,来解决 onTapGesture 导致的冲突问题。