是否可以在 SwiftUI 中为 Core Data 支持的 .listStyle(GroupedListStyle()) 实现 .onDelete 和 .onMove 功能?
Posted
技术标签:
【中文标题】是否可以在 SwiftUI 中为 Core Data 支持的 .listStyle(GroupedListStyle()) 实现 .onDelete 和 .onMove 功能?【英文标题】:Is it possible to implement.onDelete and .onMove functionality to Core Data-backed .listStyle(GroupedListStyle()) in SwiftUI? 【发布时间】:2019-08-19 17:45:01 【问题描述】:我能够使用删除和移动功能使 Core Data 支持的平面列表正常工作(没有 .listStyle 修饰符)。
但是当我尝试将列表分组时
.listStyle(GroupedListStyle())
车轮从概念上脱落。 onDelete 修饰符参数具有 IndexSet 的函数签名? -> 无效。所以我不能传入要删除的对象。
onMove 本质上是同样的问题,只是更糟。这两个修饰符都依赖于假定为可以通过 IndexSet 订阅访问的顺序值的平面数组的数据源。但我想不出如何使用平面数据源构建分组列表。
我的视图主体如下所示:
//I'm building the list using two independent arrays. This makes onDelete impossible to implement as recommended
ForEach(folders, id: \.self) folder in
Section(header: Text(folder.title) )
ForEach(self.allProjects.filter$0.folder == folder, id: \.self) project in
Text(project.title)
//this modifier is where the confusion starts:
.onDelete(perform: self.delete)
.listStyle(GroupedListStyle())
func delete (at offsets: IndexSet)
// ??.remove(atOffsets: offsets)
//Since I use two arrays to construct group list, I can't use generic remove at Offsets call. And I can't figure out a way to pass in the managed object.
func move (from source: IndexSet, to destination: Int)
////same problem here. a grouped list has Dynamic Views produced by multiple arrays, instead of the single array the move function is looking for.
【问题讨论】:
【参考方案1】:您不能存储过滤器的结果并将其传递到 .onDelete
中的自定义删除方法吗?然后删除意味着删除 IndexSet 中的项目。可以在部分之间移动吗?还是您只是指在每个文件夹中?如果仅在每个文件夹内您可以使用相同的技巧,请使用存储的项目并手动实现移动,但您可以确定在 CoreData 中的位置。
大致思路如下:
import SwiftUI
class FoldersStore: ObservableObject
@Published var folders: [MyFolder] = [
]
@Published var allProjects: [Project] = [
]
func delete(projects: [Project])
func move(projects: [Project], set: IndexSet, to: Int)
struct MyFolder: Identifiable
let id = UUID()
var title: String
struct Project: Identifiable
let id = UUID()
var title: String
var folder: UUID
struct FoldersAndFilesView: View
var body: some View
FoldersAndFilesView_NeedsEnv().environmentObject(FoldersStore())
struct FoldersAndFilesView_NeedsEnv: View
@EnvironmentObject var store: FoldersStore
var body: some View
return ForEach(store.folders) (folder: MyFolder) in
Section(header: Text(folder.title) )
FolderView(folder: folder)
.listStyle(GroupedListStyle())
struct FolderView: View
var folder: MyFolder
@EnvironmentObject var store: FoldersStore
func projects(for folder: MyFolder) -> [Project]
return self.store.allProjects.filter project in project.folder == folder.id
var body: some View
let projects: [Project] = self.projects(for: folder)
return ForEach(projects) (project: Project) in
Text(project.title)
.onDelete
self.store.delete(projects: $0.map
return projects[$0]
)
.onMove
self.store.move(projects: projects, set: $0, to: $1)
【讨论】:
感谢您的快速回复。让我玩弄它。 祝你好运。一个提示:将 section 内的所有内容移动到它自己的视图中,然后您可以在var body: some View
-method 的顶层按住 let projects = <filtering>
并在 .onDelete
和 .onMove
中访问它使用和传递。为此,您需要在子视图中进行存储访问,但要调用移动和删除方法。
我添加了我认为它可以工作的方式。这是否为您解决了问题,还是我误读了一些要求? :D .onDelete 使用指向当前提供的数组的索引,这就是为什么它在每次重绘时计算并且总是有可以被 .onDelete 引用的项目,这就是它的巧妙之处。然后隔离对象只是将索引映射到临时数组并将项目提供给存储区以进行删除。
很高兴它为你解决了!唯一的问题是它每次都会重新过滤项目(或者可能只有当它们改变时,谁知道 SwiftUI 的魔法,但我对此表示怀疑),这就是我更喜欢 Chuck 的回答的原因 :-)
这个解决方案绝对有效。但它在 Xcode 11,beta 6 中触发了一个错误。当用户在 List EditMode = .active 中时,将 FolderView 结构引入内部 ForEach 循环会中断 UI 更新。希望到 GM 出来时,该错误将得到修复,这将是公认的答案。【参考方案2】:
你说得对,做你想做的事的关键是获取一组对象并将其适当地分组。在您的情况下,这是您的项目。您没有显示您的 CoreData 模式,但我希望您有一个“项目”实体和一个“文件夹”实体以及它们之间的一对多关系。您的目标是创建一个 CoreData 查询,该查询创建该项目数组并按文件夹对它们进行分组。那么真正的关键是使用 CoreData 的 NSFetchedResultsController 使用 sectionNameKeyPath 创建组。
把我的整个项目发给你是不切实际的,所以我会尽量给你足够多的工作代码来为你指明正确的方向。有机会的话,我会把这个概念添加到我刚刚发布在 GitHub 上的示例程序中。 https://github.com/Whiffer/SwiftUI-Core-Data-Test
这是您列表的精髓:
@ObservedObject var dataSource =
CoreDataDataSource<Project>(sortKey1: "folder.order",
sortKey2: "order",
sectionNameKeyPath: "folderName")
var body: some View
List()
ForEach(self.dataSource.sections, id: \.name) section in
Section(header: Text(section.name.uppercased()))
ForEach(self.dataSource.objects(forSection: section)) project in
ListCell(project: project)
.listStyle(GroupedListStyle())
CoreDataDataSource 部分:
let frc = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: McDataModel.stack.context,
sectionNameKeyPath: sectionNameKeyPath,
cacheName: nil)
frc.delegate = self
public func performFetch()
do
try self.frc.performFetch()
catch
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
private var fetchedObjects: [T]
return frc.fetchedObjects ?? []
public var sections: [NSFetchedResultsSectionInfo]
self.performFetch()
return self.frc.sections!
public func objects(forSection: NSFetchedResultsSectionInfo) -> [T]
return forSection.objects as! [T]
public func move(from source: IndexSet, to destination: Int)
self.reorder(from: source, to: destination, within: self.fetchedObjects)
【讨论】:
1) 您描述的实体结构(文件夹和项目之间的一对多) 2) 使用 FetchedResultsController 和按 sectionNameKeyPath 参数分组的对象是一种熟悉的模式。 3)在您的 CoreDataDataSource 示例中使用泛型是王牌。虽然将 FetchedResultsController 绑定到 SwiftUI 感觉有点像科学怪人,但 frc 是专门为 Core Data 中的分组表而设计的。感谢 F*** 和 Chuck H 让我摆脱困境。 我大大增强了我的 GitHub 示例项目,向您展示了在使用嵌套 ForEach 循环时如何实现 .onDelete 和 .onMove。解决方案并不完全直截了当。看看 AttributesGroupedView 和我大大增强的 CoreDataDataSource。 感谢您的 GitHub CoreData + SwiftUI 的链接。我一直在参考它以供我使用,到目前为止,它运行得非常好。【参考方案3】:如果您想轻松地从分段中删除内容(不必分组!)List
,您需要利用嵌套。考虑一下您有以下情况:
List
ForEach(self.folders) folder in
Section(header: folder.title)
ForEach(folder.items) project in
ProjectCell(project)
现在您要设置.onDelete
。所以让我们放大Section
声明:
Section(header: Text(...))
...
.onDelete deletions in
// you have access to the current `Folder` at this level of nesting
// this is confirmed to work with singular deletion, not multi-select deletion
// I would hope that this actually gets called once per section that contains a deletion
// but that is _not_ confirmed
guard !deletions.isEmpty else return
self.delete(deletions, in: folder)
func delete(_ indexes: IndexSet, in folder: Folder)
// you can now delete this bc you have your managed object type and indexes into the project structure
【讨论】:
谢谢,卡住了,暂时搬走了。等我回来再好好看看。以上是关于是否可以在 SwiftUI 中为 Core Data 支持的 .listStyle(GroupedListStyle()) 实现 .onDelete 和 .onMove 功能?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在 SwiftUI 中为 TextField 添加字距调整?
如何在 SwiftUI 中为 Start->Max->Start 设置动画?
是否可以使用 SwiftUI 将键盘上的“返回”键更改为“完成”?