使用SwiftData的 OutlineGroup
我的SwiftData模型由一个名为 Entry 的模型实体组成。目前我把它们显示在一个 List 中:
@Model
final class Entry: Decodable, Identifiable {
var fullName: String = ""
// ...
}
struct EntriesView: View {
//...
@Query(sort: \Entry.fullName) private var entries: [Entry]
var body: some View {
List {
ForEach(entries, id: \.fullName) { entry in
NavigationLink {
EntryView(entry: entry) // EntryView shows the Entry details
} label: {
Text(entry.fullName)
}
}
}
}
}
我想做的是在列表中添加一个文件夹结构,类似于Xcode。所以会有带有子文件夹的文件夹结构,每个文件夹包含一个或多个 Entry。
我找到了Apple的这个示例:
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "📄 \(name)"
case .some(let children):
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents", children: [])
])
])
OutlineGroup(data, children: \.children) { item in
Text("\(item.description)")
}
但是我不明白如何将其翻译成我的情况。我应该为文件夹创建一个 SwiftData 实体吗?
编辑:只有文件夹应该有子项,条目是最终叶子。
解决方案
有两个主要修改你需要对 FileItem 在文档示例中配合SwiftData工作。
- 为反向关系添加一个额外的
parent属性。你可以使用parent == nil作为谓词来仅获取顶层项。 - 添加一个
isFolder属性以区分文件夹和非文件夹。OutlineGroup指向[T]?的键路径,以区分“not expandable at all”(nil)和“expandable but no children”([])。然而,在SwiftData中并没有这样的区分。
@Model
class FileItem: CustomStringConvertible {
var name: String
@Relationship(inverse: \FileItem.parent)
var children: [FileItem]
var parent: FileItem?
var isFolder: Bool
// this is the key path we will pass to OutlineGroup
var outlineGroupChildren: [FileItem]? {
if isFolder {
children
} else {
nil
}
}
init(name: String, children: [FileItem]? = nil) {
self.name = name
self.children = children ?? []
isFolder = children != nil
}
var description: String {
if isFolder {
return "📄 \(name)"
} else {
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
struct ContentView: View {
@Query(filter: #Predicate<FileItem> { $0.parent == nil }) private var fileItems: [FileItem]
@Environment(\.modelContext) var ctx
var body: some View {
Button("Add data") {
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents", children: [])
])
])
ctx.insert(data)
}
List {
OutlineGroup(fileItems, children: \.outlineGroupChildren) { item in
Text("\(item.description)")
}
}
.padding()
}
}
回到你的用例,文件夹和条目都将由这个 FileItem 类型表示。文件夹是 FileItems,其中 isFolder 为true,条目是 FileItems,其中 isFolder 为false。
如果你想使用不同的 @Model 类来建模文件夹和条目(例如 Folder 和 Entry),那么你不能直接使用 OutlineGroup。要使用 OutlineGroup,叶子必须由与父项相同的类型表示。你需要把 @Model 对象映射成如下的枚举:
enum FileItem {
case entry(Entry)
case folder(Folder)
var outlineGroupChildren: [FileItem]? { ... }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。