PAYATHON 2026

SwiftUI 的 .draggable 导致条目无法选中,如何兼容拖放与导航?

数据小许

优先把文件夹的拖动范围缩小到标签

根据排查结果,可以先移除递归 FolderRow 外层的 .draggable(subfolder.name),把文件夹的 .draggable 放到 DisclosureGroup 的 label 中,让用户从文件夹名称或图标开始拖动。

你已经观察到,去掉子文件夹外层的 .draggable 后,条目自身的拖动和 NavigationLink 都能工作。因此可以保留条目上的 .draggable(entry.name),先调整文件夹拖动修饰符的位置。

这只是根据现有代码和排查现象提出的修复方向。由于没有提供具体的 macOS、Xcode 版本,也没有调整后的运行结果,不能断言某个系统版本存在确定的框架缺陷,或保证所有版本都会有相同表现。

为什么换成 Transferable 或 Codable 没有改善

当前的结构相当于:

FolderRow + .draggable(subfolder.name)
└── DisclosureGroup
    ├── 文件夹标签
    ├── 子文件夹
    └── NavigationLink + .draggable(entry.name)

外层 .draggable 应用在包含整个子树的视图上,子树内部又有可点击、可拖动的条目。结合禁用外层修饰符后的结果,问题更可能与嵌套视图的交互范围有关。

Transferable 和 Codable 涉及拖动数据的表达与编码,改变这些类型不会直接缩小 .draggable 所在的视图范围。现有材料也没有显示拖动数据无法传递,因此可以先调整视图结构。

修改 FolderRow 的 body

将 FolderRow 中的 body 替换为下面的代码,保留原有属性和 handleDrop(of:) 方法:

var body: some View {
    DisclosureGroup(isExpanded: $isExpanded) {
        ForEach(folder.subfolders) { subfolder in
            FolderRow(folder: subfolder)
        }

        ForEach(folder.entries) { entry in
            NavigationLink {
                VStack {
                    Text(entry.name)
                    Text(entry.detail)
                }
            } label: {
                EntryRow(entry: entry)
            }
            .draggable(entry.name)
        }
    } label: {
        if folder.parentFolder != nil {
            Label(folder.name, systemImage: "folder")
                .draggable(folder.name)
        } else {
            Label(folder.name, systemImage: "folder")
        }
    }
    .dropDestination(for: String.self) { names, _ in
        handleDrop(of: names)
    }
}

这段代码有两处变化:

  • 递归创建 FolderRow 时,不再给整个子树添加 .draggable。
  • 子文件夹的拖动入口放在文件夹标签上,不再包住下面的条目链接。

folder.parentFolder != nil 沿用了原代码中只有子文件夹可以作为拖动来源的行为。根文件夹仍然可以接收拖放。

.dropDestination 暂时保持原位,先验证拖动来源范围是否导致了选择失效。如果后续发现嵌套文件夹接收拖放的目标不符合预期,可以单独测试将接收区域也收窄到文件夹标签。这样会改变可放置区域,需要另行验证。

验证交互与数据边界

修改后,分别检查根文件夹下的条目、多层子文件夹中的条目,以及文件夹标签本身:

  1. 单击条目能否打开对应详情。
  2. 拖动条目到其他文件夹后,entry.folder 是否更新。
  3. 从子文件夹的名称或图标开始拖动,能否移动文件夹。
  4. 文件夹展开、折叠后,条目点击和拖动是否仍然正常。

这里的“选中”需要区分两种需求:原代码通过 NavigationLink 打开详情,没有使用 List(selection:) 管理显式选择状态。如果还需要读取当前选中项、支持多选或由代码控制选择,就需要另外设计选择状态。这可以与当前链接无法响应的问题分开处理。

现有 handleDrop(of:) 还有几个独立的数据边界需要处理:

  • 拒绝把文件夹放入自身或其后代,否则可能形成循环关系。
  • Folder.name 与 Entry.name 分别唯一,并不能保证两类对象之间名称不重复。当前代码先查找 Entry,遇到同名文件夹时可能将其识别成条目;正式实现可使用带对象类型的稳定标识作为拖动数据。
  • 只有实际接受并处理了拖动对象时,才返回 true。当前代码即使没有匹配对象,也可能返回成功。

这些问题需要在拖放逻辑中处理,但不能将其视为此次 NavigationLink 失效的已证实原因。

备注:内容仅供参考。