SwiftUI enthält einen Verweis auf ein gelöschtes Kerndatenobjekt, das einen Absturz verursacht

8

Ich finde es unmöglich, Kerndaten mit SwiftUI zu verwenden, da die Navigationslinkansicht beim Übergeben von Kerndaten an eine beobachtete Objektvariable der Ansicht auch nach dem Verschwinden der Ansicht einen Verweis auf das Objekt enthält, sobald ich die lösche Objekt aus dem Kontext Die App stürzt ab, ohne Fehlermeldungen.

Ich habe dies bestätigt, indem ich die Variable des Kerndatenobjekts optional in ein Ansichtsmodell eingeschlossen und das Objekt direkt nach der Aktion zum Löschen des Kontexts auf Null gesetzt habe und die App einwandfrei funktioniert. Dies ist jedoch keine Lösung, da ich das Kerndatenobjekt benötige an die schnellen ui-ansichten zu binden und die quelle der wahrheit zu sein. Wie soll das funktionieren? Ich kann mit SwiftUI anscheinend nichts aus der Ferne komplex machen.

Ich habe versucht, das übergebene Kerndatenobjekt einem optionalen @State zuzuweisen, aber dies funktioniert nicht. Ich kann @Binding nicht verwenden, da es sich um ein abgerufenes Objekt handelt. Und ich kann keine Variable verwenden, da Swiftui-Steuerelemente Bindungen erfordern. Es ist nur sinnvoll, ein @ObservedObject zu verwenden, aber dies kann nicht optional sein. Wenn das ihm zugewiesene Objekt gelöscht wird, stürzt die App ab, da ich es nicht auf Null setzen kann.

Hier ist das Kerndatenobjekt, das standardmäßig ein beobachtbares Objekt ist:

class Entry: NSManagedObject, Identifiable {

    @NSManaged public var date: Date
}

Hier ist eine Ansicht, die ein Kerndateneingabeobjekt an eine andere Ansicht übergibt.

struct JournalView: View {

    @Environment(\.managedObjectContext) private var context

    @FetchRequest(
        entity: Entry.entity(),
        sortDescriptors: [],
        predicate: nil,
        animation: .default
    ) var entries: FetchedResults<Entry>

    var body: some View {
        NavigationView {
            List {
                ForEach(entries.indices) { index in
                    NavigationLink(destination: EntryView(entry: self.entries[index])) {
                        Text("Entry")
                    }
                }.onDelete { indexSet in
                    for index in indexSet {
                        self.context.delete(self.entries[index])
                    }
                }
            }
        }
    }
}

Hier ist die Ansicht, die auf alle Attribute des übergebenen Kerndateneingabeobjekts zugreift. Einmal lösche ich diesen Eintrag aus jeder Ansicht, auf die hier übrigens immer noch verwiesen wird, und führt zum sofortigen Absturz der App. Ich glaube, dies hat auch etwas damit zu tun, dass der Navigationslink alle Zielansichten initialisiert, bevor überhaupt auf sie zugegriffen wird. Was keinen Sinn macht, warum es das tun würde. Ist das ein Fehler oder gibt es einen besseren Weg, dies zu erreichen?

Ich habe sogar versucht, das Löschen onDisappear ohne Erfolg durchzuführen. Selbst wenn ich das Löschen aus der JournalView durchführe, stürzt es immer noch ab, da der NavigationLink immer noch auf das Objekt verweist. Interessanterweise stürzt es nicht ab, wenn ein Navigationslink gelöscht wird, auf den noch nicht geklickt wurde.

struct EntryView: View {

    @Environment(\.managedObjectContext) private var context
    @Environment(\.presentationMode) private var presentationMode

    @ObservedObject var entry: Entry

    var body: some View {
        Form {

            DatePicker(selection: $entry.date) {
                Text("Date")
            }

            Button(action: {
                self.context.delete(self.entry)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Delete")
            }
        }
    }
}

AKTUALISIEREN

Der Absturz führt mich zur ersten Verwendung des Eintrags in der EntryView und lautet Thread 1: EXC_BAD_INSTRUCTION (Code = EXC_I386_INVOP, Subcode = 0x0). Dies ist die einzige Nachricht, die ausgelöst wird.

Die einzige Möglichkeit, die ich mir vorstellen kann, besteht darin, dem Kerndatenobjekt "isDeleted" eine Eigenschaft hinzuzufügen und diese auf "true" zu setzen, anstatt zu versuchen, sie aus dem Kontext zu löschen. Wenn die App beendet wird oder gestartet wird, kann ich alle Einträge löschen und löschen, die gelöscht werden. Nicht ideal und würde es vorziehen, herauszufinden, was hier falsch ist, da ich anscheinend nichts anderes mache als das MasterDetailApp-Beispiel, das zu funktionieren scheint.

SybrSyn
quelle
Was für ein Ärger! irgendein Update auf diesem @SybrSyn ?!
Fattie

Antworten:

4

Ich hatte im Grunde das gleiche Problem. Es scheint, dass SwiftUI jede Ansicht sofort lädt, sodass die Ansicht mit den Eigenschaften des vorhandenen CoreData-Objekts geladen wurde. Wenn Sie es in der Ansicht löschen, in der über @ObservedObject auf einige Daten zugegriffen wird, stürzt es ab.

Meine Problemumgehung:

  1. Die Löschaktion wurde verschoben, aber über das Benachrichtigungscenter beendet
    Button(action: {
      //Send Message that the Item  should be deleted
       NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)

       //Navigate to a view where the CoreDate Object isn't made available via a property wrapper
        self.presentationMode.wrappedValue.dismiss()
      })
      {Text("Delete Item")}

Sie müssen einen Notification.name definieren, wie:

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
  1. Suchen Sie in der entsprechenden Ansicht nach der Nachricht löschen

// Receive Message that the Disease should be deleted
    .onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in

        //1: Dismiss the View (IF It also contains Data from the Item!!)
        self.presentationMode.wrappedValue.dismiss()

        //2: Start deleting Disease - AFTER view has been dismissed
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
    }
  1. Seien Sie sicher in Ihren Ansichten, in denen auf einige CoreData-Elemente zugegriffen wird - Suchen Sie nach isFault!

    VStack{
         //Important: Only display text if the disease item is available!!!!
           if !diseaseDetail.isFault {
                  Text (self.diseaseDetail.text)
            } else { EmptyView() }
    }

Ein bisschen hacky, aber das funktioniert bei mir.

sTOOs
quelle
Super, danke für diese Lösung. Ich werde es versuchen müssen. Die Problemumgehung, die ich mir in der Zwischenzeit ausgedacht habe, bestand darin, der Entität mit dem Namen "inTrash" ein Attribut hinzuzufügen und dieses beim Löschen auf "true" zu setzen, den Papierkorb in den Abrufanforderungen herauszufiltern und den gesamten Papierkorb beim Start zu bereinigen. Dies ist jedoch nicht ideal Ich arbeite auch für mich.
SybrSyn
0

Ich bin auf dasselbe Problem gestoßen und habe keine Lösung für das Grundproblem gefunden. Aber jetzt "schütze" ich die Ansicht, die die referenzierten Daten verwendet, wie folgt:

var body: some View {
    if (clip.isFault) {
        return AnyView(EmptyView())
    } else {
        return AnyView(actualClipView)
    }
}

var actualClipView: some View {
    // …the actual view code accessing various fields in clip
}

Das fühlt sich auch hackig an, funktioniert aber vorerst gut. Es ist weniger komplex als die Verwendung einer Benachrichtigung, um das Löschen zu verschieben, aber dank sTOOs Antwort auf den Hinweis mit .isFault!

Benjamin Graf
quelle