Core data 是否可以在SwiftUI中将.onDelete和.onMove功能实现为核心数据备份的.listStyle(GroupedListStyle())呢?

Core data 是否可以在SwiftUI中将.onDelete和.onMove功能实现为核心数据备份的.listStyle(GroupedListStyle())呢?,core-data,swiftui,Core Data,Swiftui,我能够得到一个核心数据支持的平面列表(不带.listStyle修饰符)和删除和移动功能 但当我试图将列表分组时 .listStyle(GroupedListStyle()) 车轮从概念上脱落。onDelete修饰符参数的函数签名为IndexSet?->无效的因此,我无法传入要删除的对象 onMove本质上是相同的问题,只是更糟。这两个修饰符都依赖于一个数据源,假设该数据源是一个序列值的平面数组,可以通过IndexSet订阅访问该数组。但我不知道如何使用平面数据源构建分组列表 我的视图主体如下

我能够得到一个核心数据支持的平面列表(不带.listStyle修饰符)和删除和移动功能

但当我试图将列表分组时

.listStyle(GroupedListStyle()) 车轮从概念上脱落。onDelete修饰符参数的函数签名为IndexSet?->无效的因此,我无法传入要删除的对象

onMove本质上是相同的问题,只是更糟。这两个修饰符都依赖于一个数据源,假设该数据源是一个序列值的平面数组,可以通过IndexSet订阅访问该数组。但我不知道如何使用平面数据源构建分组列表

我的视图主体如下所示:

//我正在使用两个独立的数组构建列表。这使得onDelete无法按照建议实施
ForEach(文件夹,id:\.self){中的文件夹
节(标题:文本(文件夹标题)){
ForEach(self.allProjects.filter{$0.folder==folder},id:\.self){project in
文本(项目名称)
//此修饰符是混乱的开始:
}.onDelete(执行:self.delete)
}
}
}.listStyle(GroupedListStyle())
func delete(偏移量处:IndexSet){
//??移除(原子偏移:偏移)
//由于我使用两个数组来构造组列表,所以我不能使用通用的偏移量移除调用,也不能找到传入托管对象的方法。
}
func移动(从源:IndexSet到目标:Int){
////这里也有同样的问题。分组列表具有由多个数组生成的动态视图,而不是move函数所寻找的单个数组。
} 

您不能存储筛选结果并将其传递到
.onDelete
中的自定义删除方法吗?那么delete意味着删除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)
        }
    }
}

你是正确的,做你想要做的事情的关键是得到一个对象数组并对其进行适当的分组。在你的情况下,这是你的项目。您没有显示您的CoreData模式,但我希望您有一个“项目”实体和一个“文件夹”实体以及它们之间的一对多关系。您的目标是创建一个CoreData查询,以创建项目数组并按文件夹对其进行分组。然后,真正的关键是使用CoreData的NSFetchedResultsController使用sectionNameKeyPath创建组

对我来说,把我的整个项目发送给你是不现实的,所以我会尽量给你足够的工作代码,为你指明正确的方向。如果有机会,我将把这个概念添加到我刚刚在GitHub上发布的示例程序中

这是您列表的要点:

@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())
    }

如果您想轻松地从分区(不必分组!)
列表中删除内容,则需要利用嵌套。假设你有以下内容:

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)
        }
    }
}
列表{
ForEach(self.folders){中的文件夹
节(标题:folder.title){
ForEach(folder.items){中的项目
ProjectCell(项目)
}
}
}
}
现在您需要设置
.onDelete
。因此,让我们放大
部分
声明:

节(标题:文本(…){
...
}
.onDelete{中的删除
//您可以在此嵌套级别访问当前的“文件夹”
//这已确认可用于单选删除,而不是多选删除
//我希望每个包含删除的部分都能调用一次
//但这还没有得到证实
guard!deletions.isEmpty else{return}
删除(删除,在:文件夹中)
}
func delete(uindex:IndexSet,在文件夹:folder中){
//您现在可以删除此bc,因为您已将托管对象类型和索引添加到项目结构中
}

感谢您的快速响应。让我来玩玩,祝你好运。一个提示:将节中的所有内容移动到它自己的视图中,然后您可以在
var body:some view
-方法的顶层按住
let projects=
,并在
中访问它。onDelete
。onMove
使用并传递。为此,您需要在子视图中具有存储访问权限才能调用move和delete方法。这是否为您解决了问题,还是我误读了一些要求D.onDelete使用指向当前提供的数组的索引,这就是为什么每次重画时都会计算它,并且总是有可以被.onDelete引用的项目,这就是它的优点。然后,隔离对象只是将索引映射到临时数组,并将项目交给存储区删除。很高兴它为您解决了这个问题!唯一的问题是它每次都会重新过滤项目(或者可能只有在项目发生变化时,谁知道SwiftUI magic,但我对此表示怀疑),这正是我更喜欢Chuck答案的地方:-)这个解决方案绝对有效。但它在Xcode 11 beta 6中触发了一个bug。将FolderView结构引入内部ForEach循环会在用户处于List EditMode=.active时中断UI更新。希望在GM退出时,该错误将得到修复,这将是公认的答案。1)您描述的实体结构(文件夹和项目之间的一对多)2)使用FetchedResultsController和按sectionNameKeyPath参数分组的对象是一种熟悉的模式。3) 在CoreDataDataSource示例中使用泛型是aces。虽然把一个抓取的结果控制器栓到SwiftUI上感觉有点像弗兰肯斯坦怪兽,