เป็นไปได้หรือไม่ที่จะใช้ฟังก์ชันการทำงาน onDelete และ .onMove กับ Core Data-backed .listStyle(GroupedListStyle()) ใน SwiftUI

ฉันสามารถทำให้รายการแฟลตที่ได้รับการสนับสนุน 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.
        } 

person Small Talk    schedule 19.08.2019    source แหล่งที่มา


คำตอบ (3)


คุณไม่สามารถจัดเก็บผลลัพธ์ของตัวกรองและส่งต่อภายใน .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)
        }
    }
}
person Fabian    schedule 19.08.2019
comment
ขอบคุณสำหรับการตอบสนองอย่างรวดเร็ว ให้ฉันเล่นกับมัน - person Small Talk; 19.08.2019
comment
ขอให้โชคดี. เคล็ดลับเดียว: ย้ายทุกอย่างภายใน ส่วน ภายในมุมมองของตัวเอง จากนั้นคุณสามารถกด let projects = <filtering> ที่ระดับบนสุดของวิธี var body: some View และเข้าถึงภายใน .onDelete และ .onMove เพื่อใช้และส่งต่อ สำหรับสิ่งนี้คุณต้องเข้าถึงร้านค้าภายในมุมมองย่อยเพื่อเรียกวิธีการย้ายและลบ - person Fabian; 19.08.2019
comment
ฉันเพิ่มวิธีที่ฉันคิดว่ามันจะใช้งานได้ สิ่งนี้จะแก้ไขให้คุณได้หรือฉันอ่านข้อกำหนดบางอย่างผิดไป? :D .onDelete ใช้ดัชนีที่ชี้ไปยังอาร์เรย์ที่ให้มาในปัจจุบัน นั่นคือสาเหตุว่าทำไมจึงมีการคำนวณในการวาดใหม่แต่ละครั้งและมีโปรเจ็กต์ที่สามารถอ้างอิงได้โดย .onDelete เสมอ นั่นคือสิ่งที่เรียบร้อยเกี่ยวกับมัน การแยกวัตถุเป็นเพียงการแมปดัชนีกับอาร์เรย์ชั่วคราวและให้โปรเจ็กต์แก่ร้านค้าเพื่อลบ - person Fabian; 19.08.2019
comment
ดีที่มันได้ผลสำหรับคุณ! ปัญหาเดียวเกี่ยวกับเรื่องนี้ก็คือมันจะกรองโครงการในแต่ละครั้ง (หรืออาจเป็นเฉพาะในกรณีที่พวกเขาเปลี่ยนแปลงใครจะรู้เวทมนตร์ SwiftUI แต่ฉันสงสัยมัน) นั่นคือสิ่งที่ฉันชอบเกี่ยวกับคำตอบของ Chuck มากกว่า :-) - person Fabian; 21.08.2019
comment
หลังจากพยายามแล้ว ฉันจะลองวิธีอื่นดู ข้อมูลที่ฝังอยู่ใน ForEach FolderView แต่ละรายการทำงานได้ดีเพื่อจัดเตรียมออบเจ็กต์ที่จำเป็นสำหรับ .onMove และ .onDelete แต่การเพิ่ม FolderView ในวง ForEach ภายในดูเหมือนว่าจะขัดขวางการอัปเดตมุมมองแบบไดนามิกของ SwiftUI และสิ่งต่าง ๆ ก็มีข้อบกพร่องทางสายตา ฉันได้ลองสร้างใหม่ตั้งแต่ต้น แต่ไม่สามารถรับการอัปเดตมุมมองที่เชื่อถือได้ stackoverflow.com/questions/57627666/ ฉันจะเล่นกับวิธี frc ด้านล่างและดูว่าฉันมีโชคดีขึ้นหรือไม่ คุณเสนอวิธีแก้ปัญหาที่มั่นคง - person Small Talk; 24.08.2019
comment
โซลูชันนี้ใช้งานได้จริง แต่จะทำให้เกิดข้อผิดพลาดใน Xcode 11 เบต้า 6 การแนะนำโครงสร้าง FolderView ลงในวง ForEach ภายในจะแบ่งการอัปเดต UI เมื่อผู้ใช้ใน List EditMode = .active หวังว่าเมื่อ GM ออกมา ข้อผิดพลาดนั้นจะได้รับการแก้ไข และนี่จะเป็นคำตอบที่ได้รับการยอมรับ - person Small Talk; 25.08.2019

คุณถูกต้องว่ากุญแจสำคัญในการทำสิ่งที่คุณต้องการคือการได้รับวัตถุหนึ่งอาร์เรย์และจัดกลุ่มอย่างเหมาะสม ในกรณีของคุณ มันเป็นโครงการของคุณ คุณไม่ได้แสดงสคีมา CoreData ของคุณ แต่ฉันคาดหวังว่าคุณจะมีเอนทิตี "โปรเจ็กต์" และเอนทิตี "โฟลเดอร์" และมีความสัมพันธ์แบบหนึ่งต่อกลุ่มระหว่างสิ่งเหล่านั้น เป้าหมายของคุณคือการสร้างแบบสอบถาม CoreData ที่สร้างอาร์เรย์ของโปรเจ็กต์นั้นและจัดกลุ่มตามโฟลเดอร์ กุญแจที่แท้จริงคือการใช้ NSFetchedResultsController ของ CoreData เพื่อสร้างกลุ่มโดยใช้ 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)
    }
person Chuck H    schedule 20.08.2019
comment
1) โครงสร้างเอนทิตีตามที่คุณอธิบาย (แบบหนึ่งต่อกลุ่มระหว่างโฟลเดอร์และโปรเจ็กต์) 2) การใช้ FetchedResultsController กับออบเจ็กต์ที่จัดกลุ่มตามพารามิเตอร์ SectionNameKeyPath เป็นรูปแบบที่คุ้นเคย 3) การใช้ชื่อสามัญในตัวอย่าง CoreDataDataSource ของคุณคือเอซ แม้ว่าจะให้ความรู้สึกเหมือนเป็นสัตว์ประหลาดแฟรงเกนสไตน์เล็กน้อยที่ส่ง FetchedResultsController ไปยัง SwiftUI แต่ frc นั้นมีจุดประสงค์เพื่อจัดกลุ่มตารางจาก Core Data ขอบคุณทั้ง Fabian และ Chuck H ที่ทำให้ฉันไม่ติดขัด - person Small Talk; 20.08.2019
comment
ฉันปรับปรุงโปรเจ็กต์ตัวอย่าง GitHub ของฉันอย่างมากเพื่อแสดงวิธีการใช้งาน .onDelete และ .onMove เมื่อใช้ ForEach loops ที่ซ้อนกัน การแก้ปัญหาไม่ได้ตรงไปตรงมาโดยสิ้นเชิง ดู AttributesGroupedView และ CoreDataDataSource ที่ได้รับการปรับปรุงอย่างมากของฉัน - person Chuck H; 21.08.2019
comment
ขอบคุณสำหรับลิงก์ไปยัง GitHub CoreData + SwiftUI ของคุณ ฉันอ้างอิงมันสำหรับการใช้งานของฉัน และจนถึงตอนนี้ มันใช้งานได้ดีจริงๆ - person Small Talk; 27.08.2019

หากคุณต้องการลบสิ่งต่าง ๆ ออกจากส่วนอย่างง่ายดาย (ไม่จำเป็นต้องจัดกลุ่ม!) 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
}
person Procrastin8    schedule 18.12.2019
comment
ขอบคุณครับ ติดเรื่องนี้และย้ายออกไปชั่วคราว จะกลับมาดูใหม่อีกครั้งครับ.. - person Small Talk; 22.12.2019