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 暂时保持原位,先验证拖动来源范围是否导致了选择失效。如果后续发现嵌套文件夹接收拖放的目标不符合预期,可以单独测试将接收区域也收窄到文件夹标签。这样会改变可放置区域,需要另行验证。
验证交互与数据边界
修改后,分别检查根文件夹下的条目、多层子文件夹中的条目,以及文件夹标签本身:
- 单击条目能否打开对应详情。
- 拖动条目到其他文件夹后,
entry.folder是否更新。 - 从子文件夹的名称或图标开始拖动,能否移动文件夹。
- 文件夹展开、折叠后,条目点击和拖动是否仍然正常。
这里的“选中”需要区分两种需求:原代码通过 NavigationLink 打开详情,没有使用 List(selection:) 管理显式选择状态。如果还需要读取当前选中项、支持多选或由代码控制选择,就需要另外设计选择状态。这可以与当前链接无法响应的问题分开处理。
现有 handleDrop(of:) 还有几个独立的数据边界需要处理:
- 拒绝把文件夹放入自身或其后代,否则可能形成循环关系。
Folder.name与Entry.name分别唯一,并不能保证两类对象之间名称不重复。当前代码先查找Entry,遇到同名文件夹时可能将其识别成条目;正式实现可使用带对象类型的稳定标识作为拖动数据。- 只有实际接受并处理了拖动对象时,才返回
true。当前代码即使没有匹配对象,也可能返回成功。
这些问题需要在拖放逻辑中处理,但不能将其视为此次 NavigationLink 失效的已证实原因。
备注:内容仅供参考。
