NSManagedObject of class 'was auch immer' must have a valid NSEntityDescription

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • NSManagedObject of class 'was auch immer' must have a valid NSEntityDescription

    Hallo zusammen,

    ich komme gerade mit der Kombination von SwiftUI und CoreData gar nicht mehr zurecht.

    Ich habe eine SwiftUI-App. In dieser gibt es in der Datenbank eine Tabelle genannt "Mandanten". Die gleiche habe ich auch in der Datei "Persistence" erwähnt:

    Quellcode: Persistence.swift

    1. import CoreData
    2. struct PersistenceController {
    3. static let shared = PersistenceController()
    4. let container: NSPersistentContainer
    5. init() {
    6. // Hier muss in Klammern der Name der DB-Datei eingetragen sein
    7. container = NSPersistentContainer(name: "Mandanten")
    8. // Der Container wird hier geladen, falls Fehler auftreten, werden die hier ausgeworfen:
    9. container.loadPersistentStores {(storeDescription, error) in
    10. if let error = error as NSError? {
    11. fatalError("Unerwarteter Fehler: \(error)")
    12. }
    13. }
    14. }
    15. }
    Alles anzeigen




    Dann habe ich der App an sich mitgeteilt, dass es einen PersistenceContainer gibt:

    Quellcode

    1. import SwiftUI
    2. @main
    3. struct MariaApp: App {
    4. let persistenceContainer = PersistenceController.shared
    5. static let DerMitarbeiter = Mitarbeiter()
    6. var body: some Scene {
    7. WindowGroup {
    8. ContentView(mitarbeiter: MariaApp.DerMitarbeiter)
    9. .environment(\.managedObjectContext, persistenceContainer.container.viewContext)
    10. }
    11. }
    12. }
    Alles anzeigen
    Soweit so gut. In einer Übungsapp habe ich dann die DB direkt aus einer SwiftUI-View heraus kontaktiert, hier ist es anders. Es gibt eine andere Swiftdatei, diese enthält die Klasse "Mitarbeiter". Der Mitarbeiter loggt sich ein mit seinen Anmeldedaten, die App kontaktiert eine Webanwendung und schaut, ob sioe in der dortigen MySQL-DB diesen Mitarbeiter finden kann. Wenn ja, werden per JSON diverse Daten zurückübermittelt (unter anderem "Mandanten"), die dann in CoreData gespeichert werden sollen.

    Zunächst habe ich oben in der Datei, welche die besagte Klasse beinhaltet, folgendes geschrieben:

    Quellcode: Mitarbeiter.swift

    1. class Mitarbeiter: ObservableObject {
    2. @Environment(\.managedObjectContext) private var viewContext
    3. @FetchRequest(sortDescriptors: [])
    4. var gespeicherte_mandanten: FetchedResults<Mandanten>
    5. // .... und viel mehr Code....
    In der Funktion, welche die aus JSON erhaltenen Daten in CoreData übertragen soll, steht dann:

    Quellcode: Mitarbeiter.swift

    1. let neuer_mandant = Mandanten(context: viewContext)
    2. neuer_mandant.unternehmensname = firma.unternehmensname
    3. neuer_mandant.id = firma.ID!
    4. neuer_mandant.fachbereich = firma.fachbereich
    5. // ... und noch ein paar andere Eigenschaften
    6. save_mandanten()
    Letzte Zeile bezieht sich auf diese Funktion:

    Quellcode: Mitarbeiter.swift

    1. private func save_mandanten() {
    2. do {
    3. try viewContext.save()
    4. } catch {
    5. let error = error as NSError
    6. fatalError("Fehler beim Speichern eines Mandanten: \(error)")
    7. }
    8. }

    Soweit bekomme ich keine Fehler, erst wenn ich die App tatsächlich starte. Dann heißt es plötzlich "NSManagedObject of class 'Mandanten' must have a valid NSEntityDescription".

    Da war ich letztes Jahr als Swift-Anfänger soooo stolz, endlich mal CoreData anwenden zu können - zack! Mit den letzten Änderungen alles wieder dahin. In einer Swift UI App gibt es keine Datei "AppDelegate" mehr, alles wieder anders, es funktioniert nicht mehr. Wo ist der Fehler? Hat jemand eine Idee? Vielen Dank im Voraus!

    VG Arek
  • Könntest Du vielleicht einen Screenshot o. ä. von Deinem Core Data Datenmodell posten? Ich kenne mich zwar nicht in SwiftUI aus, habe aber das Gefühl, dass die doppelte Namensgebung "Mandanten" für Verwirrung sorgt, weil sie einmal das Managed Object Model und einmal eine Entity beschreibt.

    Ich würde derartige Dopplungen tunlichst vermeiden, um im Code immer Klarheit zu haben, was adressiert wird: Hier zum Beispiel beim persistentContainer vs. Anlegen eines neuen Objektes. Meine Vermutung ist, dass Dein Entity tatsächlich anders heisst. Vielleicht nur "Mandant"?

    Vielleicht liege ich aber auch vollkommen faslch und Du müsstest nur eine entityDescription zum Anlegen neuer Objekte nutzen ... so mache ich es in ObjectiveC, bin aber dann mangels Swift- / SwiftUI-Kenntnissen raus.

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • [Blockierte Grafik: https://flori-software.de/CoreData.png]

    Bitte - bin für jede Idee dankbar!

    Anbei noichmal die ganze Datei Mitarbeiter.swift:

    Quellcode

    1. //
    2. // Mitarbeiter.swift
    3. // Maria
    4. //
    5. // Created by Arkadiusz Paluszek on 03.10.21.
    6. //
    7. import Foundation
    8. import SwiftUI
    9. import CoreData
    10. class Mitarbeiter: ObservableObject {
    11. @Environment(\.managedObjectContext) private var viewContext
    12. @FetchRequest(sortDescriptors: [])
    13. var gespeicherte_mandanten: FetchedResults<Mandanten>
    14. var erstes_einloggen = true
    15. var gesperrt_falscher_job = false
    16. var gesperrt_erfolglose_verknuepfung = false
    17. var zugriffscode = ""
    18. var serverUrl = ""
    19. var mandanten = [ich]()
    20. var mit_gabriel_gekoppelt = ""
    21. @Published var seite = ""
    22. init() {
    23. let domain = Bundle.main.bundleIdentifier!
    24. UserDefaults.standard.removePersistentDomain(forName: domain)
    25. UserDefaults.standard.synchronize()
    26. print(Array(UserDefaults.standard.dictionaryRepresentation().keys).count)
    27. // Auslesen der aktuellen Werte
    28. let benutzer = UserDefaults.standard
    29. self.erstes_einloggen = (benutzer.value(forKey: "erstes_einloggen") as? Bool) ?? true
    30. self.gesperrt_falscher_job = (benutzer.value(forKey: "falscher_job") as? Bool) ?? false
    31. self.gesperrt_erfolglose_verknuepfung = (benutzer.value(forKey: "falscher_job") as? Bool) ?? false
    32. // Festlegen der Ansicht
    33. if(self.erstes_einloggen) {
    34. seite = "erstes_einloggen"
    35. } else if(self.gesperrt_falscher_job) {
    36. seite = "gesperrt_falscher_job"
    37. } else if(gesperrt_erfolglose_verknuepfung) {
    38. seite = "gesperrt_erfolglose_verknuepfung"
    39. }
    40. }
    41. func erstes_einloggen_erfolgt() {
    42. let benutzer = UserDefaults.standard
    43. benutzer.setValue(false, forKey: "erstes_einloggen")
    44. // Für die Navigation
    45. self.seite = "eingabe_zugriffscode"
    46. }
    47. func falscher_job() {
    48. print("Falscher Job")
    49. // Traurig. Kann man nichts machen. Niemand ist perfekt.
    50. let benutzer = UserDefaults.standard
    51. //self.erstes_einloggen = false
    52. //self.gesperrt_falscher_job = true
    53. benutzer.setValue(false, forKey: "erstes_einloggen")
    54. benutzer.setValue(true, forKey: "falscher_job")
    55. self.seite = "gesperrt_falscher_job"
    56. }
    57. func get_url() {
    58. self.serverUrl = "http://localhost:8888/Gabriel/"
    59. self.serverUrl += "tutaj/interface_native_apps/interface.php"
    60. print(self.serverUrl);
    61. }
    62. func machineName() -> String {
    63. var systemInfo = utsname()
    64. uname(&systemInfo)
    65. let machineMirror = Mirror(reflecting: systemInfo.machine)
    66. return machineMirror.children.reduce("") { identifier, element in
    67. guard let value = element.value as? Int8, value != 0 else { return identifier }
    68. return identifier + String(UnicodeScalar(UInt8(value)))
    69. }
    70. }
    71. func logindaten_pruefen(zugangscode: String, benutzername: String, passwort: String) {
    72. self.zugriffscode = zugangscode
    73. get_url()
    74. self.serverUrl += "?aktion=mitarbeiter_erkennen&zugriffscode=\(zugangscode)&benutzername=\(benutzername)&passwort=\(passwort)"
    75. // Die UUID des Gerätes wird hinzugefügt
    76. if let uuid = UIDevice.current.identifierForVendor?.uuidString {
    77. let geraetename = machineName()
    78. serverUrl += "&uuid=\(uuid)&machine_name=\(geraetename)"
    79. }
    80. print(serverUrl)
    81. let url = URL(string: serverUrl)
    82. if let url = url {
    83. print("Die URL ist gültig")
    84. let session = URLSession(configuration: .default)
    85. let task = session.dataTask(with: url, completionHandler: {
    86. (data, response, error) in
    87. if error == nil && data != nil {
    88. print("Die URL ist verarbeitet, keine Fehler aufgetreten")
    89. // Hier werden die Funktionen aufgerufen, die nach erolfgreicher Datenübermittlung ausgeführt werden
    90. print("JSON: \(data!)")
    91. self.mit_gabriel_gekoppelt = self.parseJsonMandanten_und_Mitarbeiter(data: data!)
    92. // Dann ist auch klar, ob der LogIn-Versuch erfolgreich war
    93. if(self.mit_gabriel_gekoppelt == "erfolgreich") {
    94. self.seite = "erfolgreich_eingeloggt"
    95. } else if(self.mit_gabriel_gekoppelt == "keine_verbindung"){
    96. self.seite = "anmeldedaten_falsch"
    97. } else if (self.mit_gabriel_gekoppelt == "zugangsdaten_falsch"){
    98. }
    99. } else {
    100. if(error != nil) {
    101. print("Etwas lief schief: \(error!)")
    102. }
    103. if(data == nil) {
    104. print("Ich kann den Server nicht kontaktieren. ")
    105. }
    106. }
    107. })
    108. print("Task ausgeführt")
    109. task.resume()
    110. }
    111. else {
    112. print("Die URL ist ungültig")
    113. }
    114. }
    115. private func save_mandanten() {
    116. do {
    117. try viewContext.save()
    118. } catch {
    119. let error = error as NSError
    120. fatalError("Fehler beim Speichern eines Mandanten: \(error)")
    121. }
    122. }
    123. func parseJsonMandanten_und_Mitarbeiter(data: Data)->String {
    124. print("Parse empfangene Daten")
    125. let antwort = antwort_auf_verbindungsversuch()
    126. var ergebnis = ""
    127. let decoder = JSONDecoder()
    128. do {
    129. let antwort = try decoder.decode(antwort_auf_verbindungsversuch.self, from: data)
    130. print(antwort)
    131. print("Ich heiße \(antwort.personencode)")
    132. print("Der erste Mandant ist \(antwort.mandanten[0].unternehmensname!)")
    133. // Speichern der Mitarbeiterdaten in den UserDefaults
    134. let benutzer = UserDefaults.standard
    135. benutzer.setValue(antwort.personencode, forKey: "benutzer_personencode")
    136. benutzer.setValue(antwort.vorname, forKey: "benutzer_vorname")
    137. // Speichern der Mandantendaten in der DB
    138. for firma in antwort.mandanten {
    139. print("Hier würde ich versuchen zu speichern: \(firma.unternehmensname!)")
    140. let neuer_mandant = Mandanten(context: viewContext)
    141. // Links ist das Datenbankobjekt, rechts das Objekt, welches mit Daten vom JSON-String gefüllt wurde
    142. neuer_mandant.unternehmensname = firma.unternehmensname
    143. neuer_mandant.id = firma.ID!
    144. neuer_mandant.fachbereich = firma.fachbereich
    145. neuer_mandant.ort = firma.ort
    146. neuer_mandant.plz = firma.plz
    147. neuer_mandant.strasse = firma.strasse
    148. neuer_mandant.telefonnummer = firma.telefonnummer
    149. neuer_mandant.telefonnummer2 = firma.telefonnummer2
    150. // Anmeldedaten
    151. neuer_mandant.kurzbezeichnung = firma.kurzbezeichnung
    152. neuer_mandant.zahlencode = firma.zahlencode
    153. save_mandanten()
    154. }
    155. }
    156. catch {
    157. print("Error beim JSON-parsen")
    158. ergebnis = "keine_verbindung"
    159. }
    160. // War der LogIn-Versuch erfolgreich?
    161. if(antwort.personencode != "DerGoldene") {
    162. print("LogIn erfolgreich")
    163. ergebnis = "erfolgreich"
    164. } else {
    165. print("Zugangsdaten falsch")
    166. ergebnis = "zugangsdaten_falsch"
    167. }
    168. return ergebnis
    169. }
    170. }
    171. // Das Protokoll Codable wird verwendet, weil in diese Structs JSON-Objekte hineingeschrieben werden
    172. struct antwort_auf_verbindungsversuch: Codable {
    173. //var person: Person
    174. var personencode = ""
    175. var vorname = ""
    176. var mandanten = [ich]() // Array des Typs ich
    177. }
    178. struct ich: Codable {
    179. // Die Werte sind "optionals", weil bei erfolglosem Login keine Mandantendaten übermittelt werden
    180. var ID:Int16?
    181. var unternehmensname:String?
    182. var fachbereich:String?
    183. var strasse:String?
    184. var plz:String?
    185. var ort:String?
    186. var telefonnummer:String?
    187. var telefonnummer2:String?
    188. var kurzbezeichnung:String?
    189. var zahlencode:String?
    190. //var zugang_autorisiert = false
    191. }
    Alles anzeigen
  • Mal kurz aus dem hohlen Bauch….

    definiere doch den Mitarbeiter als Singleton.

    das geht etwa wie folgt:

    Quellcode

    1. class a {
    2. static let shared = a()
    3. private init() {}
    4. }


    Damit kannst Du aus dem gesamten Code auf die Klasse zugreifen. via

    Quellcode

    1. let mitarbeiter = a.shared
    Denn das habe ich noch nirgends gesehen, dass man eine Klasse einer Contentview als Parameter mitgibt.

    Wenn dann, schon als Environment Object

    Den ViewContext aus der Klasse, kannst auch raushauen, kannst eh nicht drauf zugreifen.

    Bastle Dir stattdessen einen CoreDataStack, den du als Singleton definierst. Das ist bequemer zu handeln, und kannst von überall drauf zugreifen. Den ViewContext brauchst du nur bei den Views, wenn du auf Coredata zugreifst.

    Wenn Du die Daten asyncron in der Contentview abrufen willst, nimm doch am besten die neue Funktion aus Swift 5.5.

    das sieht dann so aus

    Quellcode

    1. struct ContentView {
    2. var body: some View {
    3. Text(„Hallo Mitarbeiter“)
    4. .task() {
    5. let mitarbeiter = Mitarbeiter.shared
    6. await mitarbeiter.machWasDraus()
    7. //oder einfach
    8. await Mitarbeiter.shared.machEsBesser()
    9. }
    10. }
    11. }
    Alles anzeigen
    Wenn Du willst, kannst Du dann auch Fehler werfen und entsprechend drauf reagieren.

    Da ich bei deiner Klasse ein paar @… für Coredata gesehen habe, vergiss die schnell wieder. Die kannst nur in den Views verwenden. Hier schaffst du, einfach, mit den Standard Coredata funktionen.
  • Danke! Die Antworten haben mich jetzt tatsächlich weitergebracht, also:

    1. ich werde tatsächlich den Mitarbeiter als Singletoin definieren.
    2. Ich muss mich einghehender mit dem Core Data Stack befassen - sagt mir bisher nichts. :) Mal schauen, was sich in Youtube an Tutorials dazu finden lässt.
    3. zu ich() :

    Quellcode

    1. struct antwort_auf_verbindungsversuch: Codable {
    2. //var person: Person
    3. var personencode = ""
    4. var vorname = ""
    5. var mandanten = [ich]() // Array des Typs ich
    6. }
    7. struct ich: Codable {
    8. // Die Werte sind "optionals", weil bei erfolglosem Login keine Mandantendaten übermittelt werden
    9. var ID:Int16?
    10. var unternehmensname:String?
    11. var fachbereich:String?
    12. var strasse:String?
    13. var plz:String?
    14. var ort:String?
    15. var telefonnummer:String?
    16. var telefonnummer2:String?
    17. var kurzbezeichnung:String?
    18. var zahlencode:String?
    19. //var zugang_autorisiert = false
    20. }
    Alles anzeigen


    Diese zwei Structs beinhalten Daten, die der App über JSON von der Webanwnedung mitgeteilt werden. Im Prinzip an dieser Stelle:

    Quellcode

    1. func parseJsonMandanten_und_Mitarbeiter(data: Data)->String {
    2. print("Parse empfangene Daten")
    3. let antwort = antwort_auf_verbindungsversuch()
    4. var ergebnis = ""
    5. let decoder = JSONDecoder()
    6. do {
    7. let antwort = try decoder.decode(antwort_auf_verbindungsversuch.self, from: data)

    Allerdings gefällt mir bei meiner Lösung eine Sache nicht, ich habe bisher in meiner App im Prinzip zwei Objekte mit einer nahezu identischen Struktur. Das Objekt "ich", welches hier als "Codable" definiert wird, um automatisch die Daten aus JSON übernehmen zu können. Auf der anderen Seite habe ich auch das Datenmodell mit identisches Struktur:

    Quellcode

    1. let neuer_mandant = Mandanten(context: viewContext)
    2. // Links ist das Datenbankobjekt, rechts das Objekt, welches mit Daten vom JSON-String gefüllt wurde
    3. neuer_mandant.unternehmensname = firma.unternehmensname
    4. neuer_mandant.id = firma.ID!
    5. neuer_mandant.fachbereich = firma.fachbereich
    6. neuer_mandant.ort = firma.ort
    7. neuer_mandant.plz = firma.plz
    8. neuer_mandant.strasse = firma.strasse
    9. neuer_mandant.telefonnummer = firma.telefonnummer
    10. neuer_mandant.telefonnummer2 = firma.telefonnummer2
    11. // Anmeldedaten
    12. neuer_mandant.kurzbezeichnung = firma.kurzbezeichnung
    13. neuer_mandant.zahlencode = firma.zahlencode
    14. save_mandanten()
    Alles anzeigen
    Ist es sinnvoll? Es scheinzt mir, dass ich die struct ich() als "Zwischenspeicher" brauche, da ich für das Auslesen der JSON-Daten ein "Codable" Objekt benötige - dann übertrage ich die Daten vom Objekt des Typs "ich" in CoreData. Hätte es einen Weg gegeben, sich diesen Zwischenschritt zu sparen? Ich schreibe später, wie ich mit CoreDataStack etc. zurechtgekommen bin.
  • Arkadiusz Paluszek schrieb:

    2. Ich muss mich einghehender mit dem Core Data Stack befassen - sagt mir bisher nichts. :) Mal schauen, was sich in Youtube an Tutorials dazu finden lässt.
    3. zu ich() :
    Zu CorDataStack, ist eigentlich nichts anderes als ein Singleton, wo du jederzeit auf die selbe Instanz vom Viewcontext zugreifen kannst.

    zum 2. ich würde mich n die normalen Konventionen halten und die Definition Gross beginnen lassen und die instanzen klein. hilft einfach missverständnisse zu vermeiden und unnötig zu debuggen..
  • Hey,

    wie Wolf bereits sagt, ist es üblich einen Singleton-CoreDataStack zu haben, welcher Zugriff auf den CoreData-Container bzw. viewContext ermöglicht. Genau diesen Stack hast du allerdings bereits! Den legt Xcode mittlerweile automatisch bei einem neuen CoreData-Projekt an. Du hast ihn sogar in deinem Post oben bereits erwähnt: der PersistenceController in Persistence.swift. Der stellt deinen CoreData Stack dar. Im MariaApp struct schleust du diesen Stack als Environment-Objekt in die View-Hierarchy ein, sodass du in den SwiftUI Views mittels @Environment darauf zugreifen kannst. Auch @FetchRequest basiert hierauf. Aus diesem Grund funktioniert deine Mitarbeiter-Klasse auch nicht, wie Wolf bereits angemerkt hat: Die ist ja keine SwiftUI-View, somit befindet sich diese auch nicht in der View-Hierarchy und hat keinen Zugriff auf das Environment-Objekt. Möchtest du von ausserhalb auf deinen CoreData-Stack zugreifen, kannst du einfach direkt PersistenceController.shared nehmen.

    Ich bin mir nicht ganz sicher, was das Ziel deiner Mitarbeiter Klasse sein soll. Beim ersten Durchblicken wird mir dessen Funktionalität nicht schlüssig, auch aufgrund der schlechten Variablen- und Funktionsbenennenung, wenn ich das so sagen darf. ;) Namen wie falscher_job() sind wenig aussagekräftig. Ich persönlich würde englische Bezeichnungen im camelCase empfehlen, also storeInvalidJob() o.ä. Dennoch wirkt die Klasse wie ein Sammelsurium an zusammenhangslosen Funktionen. Denk objekt-orientiert! Singleton würde ich hier übrigens eher nicht nutzen. Ich bin zwar ein großer Singleton-Fan :D, habe aber in den letzten Jahren gelernt, dass das häufig durchaus auch ein Anti-Pattern ist, weil eigentlich klare Dependencies zu "Spaghetti-Code" aufgeweicht werden. Wie gesagt, bin ich mir nicht ganz sicher was die Klasse machen soll, aber falls es irgendwie den aktuell angemeldeten User darstellen sollte oder so, würde ich das vermutlich als @StateObject in der ContentView initiieren.

    Ich würde dir empfehlen, einfach mal einige CoreData-SwiftUI Tutorials (z.B. blckbirds.com/post/core-data-and-swiftui/) durchzuführen. Da lernst du einige Standard-Patterns, neben der generellen App-Architektur auch z.B. auch bzgl deiner CoreData Entity-Benennung. Diese erfolgt üblicherweise im Singular, also anstatt "Mandanten" -> "Mandant". Wirkt wie eine Kleinigkeit, ist aber durchaus hilfreich. Schließlich wirst du später einen neuen Mandanten mittels z.B. let newMandant = Mandant(...) erstellen. Wenn hier der Entity- = Klassennamen nun im Plural ist, kann das schnell Verwirrung stiften.

    So, das war nun auch eine wirre Ansammlung an gut gemeinter Tipps, ich hoffe ich habe nicht für noch mehr Verwirrung gesorgt!

    Viel Erfolg und Viele Grüße
  • Auch an dich vielen Dank! Es ist nicht selbstverständlich, dass man in wenigen Antworten tatsächlich viel hilfreiches Wissen findet - was diesmal aber auf jeden Fall der Fall ist.
    Tatsächlich ist Mitarbeiter im Augenblick mittels @StateObject in Content View abgebildet und ja - das Objekt stellt den gerade eingelogten Mitarbeiter dar. Nach der Kopplung der App mit der Webanwendung werden einige Benutzerdaten zur MariaApp übertragen (beim ersten Einloggen). MariaApp wird eine hybride App - der größte Teil der Funktionalität läuft über eine WebApp. Abhängig von den in CoreData gespeicherten Daten hat der Mitarbeiter innerhalb der WebApp dann entsprechende Rechte etc.

    Die genannten Tutorials werde ich mir anschauen.
  • Also gut, folgendes habe ich fürs Erste geändert:



    Quellcode

    1. class Mitarbeiter: ObservableObject {
    2. let db_controller = PersistenceController.shared
    3. // .... viel code ....
    4. // dabei die Funktion save_mandanten() angepasst:
    5. private func save_mandanten() {
    6. do {
    7. try db_controller.container.viewContext.save()
    8. } catch {
    9. let error = error as NSError
    10. fatalError("Fehler beim Speichern eines Mandanten: \(error)")
    11. }
    12. }
    13. // sowie hier:
    14. // Speichern der Mandantendaten in der DB
    15. for firma in antwort.mandanten {
    16. print("Hier würde ich versuchen zu speichern: \(firma.unternehmensname!)")
    17. let neuer_mandant = Mandanten(context: db_controller.container.viewContext)
    18. // Links ist das Datenbankobjekt, rechts das Objekt, welches mit Daten vom JSON-String gefüllt wurde
    19. neuer_mandant.unternehmensname = firma.unternehmensname
    20. neuer_mandant.id = firma.ID!
    21. neuer_mandant.fachbereich = firma.fachbereich
    22. neuer_mandant.ort = firma.ort
    23. neuer_mandant.plz = firma.plz
    24. neuer_mandant.strasse = firma.strasse
    25. neuer_mandant.telefonnummer = firma.telefonnummer
    26. neuer_mandant.telefonnummer2 = firma.telefonnummer2
    27. // Anmeldedaten
    28. neuer_mandant.kurzbezeichnung = firma.kurzbezeichnung
    29. neuer_mandant.zahlencode = firma.zahlencode
    30. save_mandanten()
    31. }
    32. }
    Alles anzeigen
    Allerdings ist das Ergebnis nach wie vor das Gleiche:

    Thread 15: "An NSManagedObject of class 'Mandanten' must have a valid NSEntityDescription."

    Was habe ich nicht verstanden? Wo ist mein Fehler?
  • Gut, also lasst uns alles vereinfachen und die ganze App zunächst auf das Speichern eines Datensatzes in Core Data beschränken, dazu habe ich eine kleine BeispielApp gebastelt:


    Quellcode

    1. import SwiftUI
    2. @main
    3. struct Test2App: App {
    4. let persistenceContainer = PersistenceController.shared
    5. static let DerMitarbeiter = Mitarbeiter()
    6. var body: some Scene {
    7. WindowGroup {
    8. ContentView(mitarbeiter: Test2App.DerMitarbeiter)
    9. .environment(\.managedObjectContext, persistenceContainer.container.viewContext)
    10. }
    11. }
    12. }
    Alles anzeigen




    dann dazu ContentView:

    Quellcode: ContentView.swift

    1. import SwiftUI
    2. import CoreData
    3. struct ContentView: View {
    4. // Verbindung zur Umgebung, in der eine DB-Verbindung verankert ist:
    5. @Environment(\.managedObjectContext) private var viewContext
    6. // In der Abfrage kann in die eckigen Klammern ein Sortierkriterium wie nach Datum, ASC, DESC etc. rein
    7. @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Mandant.unternehmensname, ascending: true)])
    8. var mandanten: FetchedResults<Mandant>
    9. @StateObject var mitarbeiter: Mitarbeiter
    10. var body: some View {
    11. VStack {
    12. Text("Willkommen")
    13. .padding()
    14. Button(action: {
    15. self.mitarbeiter.save_mandanten()
    16. }, label: {
    17. Text("Teste Core Data")
    18. .font(.custom("GillSans", size: 25))
    19. .foregroundColor(.black)
    20. .background(LinearGradient(gradient: Gradient(colors: [.red, .yellow]), startPoint: .topLeading, endPoint: .bottomTrailing))
    21. .cornerRadius(10)
    22. .padding(2)
    23. })
    24. }
    25. }
    26. }
    27. struct ContentView_Previews: PreviewProvider {
    28. static let DerMitarbeiter = Mitarbeiter()
    29. static var previews: some View {
    30. ContentView(mitarbeiter: DerMitarbeiter)
    31. }
    32. }
    Alles anzeigen

    dann Persistence:

    Quellcode: Persistence.swift

    1. import Foundation
    2. import CoreData
    3. struct PersistenceController {
    4. static let shared = PersistenceController()
    5. let container: NSPersistentContainer
    6. init() {
    7. // Hier muss in Klammern der Name der DB-Datei eingetragen sein
    8. container = NSPersistentContainer(name: "Mandant")
    9. // Der Container wird hier geladen, falls Fehler auftreten, werden die hier ausgeworfen:
    10. container.loadPersistentStores {(storeDescription, error) in
    11. if let error = error as NSError? {
    12. fatalError("Unerwarteter Fehler: \(error)")
    13. }
    14. }
    15. }
    16. }
    Alles anzeigen

    das Datenmodell:

    [Blockierte Grafik: https://flori-software.de/CoreData2.png]

    und schließlich die Klasse Mitarbeiter:

    Quellcode: Mitarbeiter.swift

    1. import Foundation
    2. import SwiftUI
    3. import CoreData
    4. class Mitarbeiter: ObservableObject {
    5. let database_controller = PersistenceController.shared
    6. func save_mandanten() {
    7. let neuer_mandant = Mandant(context: database_controller.container.viewContext)
    8. neuer_mandant.unternehmensname = "Apfel Computer GmbH"
    9. neuer_mandant.id = 1
    10. neuer_mandant.fachbereich = "Apfelmuss"
    11. neuer_mandant.ort = "Apfelpark"
    12. neuer_mandant.plz = "12345"
    13. neuer_mandant.strasse = "Parkstrasse"
    14. neuer_mandant.telefonnummer = "0123 456789"
    15. neuer_mandant.telefonnummer2 = "0965 543210"
    16. // Anmeldedaten
    17. neuer_mandant.kurzbezeichnung = "Apfel"
    18. neuer_mandant.zahlencode = "42"
    19. save_mandanten()
    20. print("Fertig!")
    21. }
    22. }
    Alles anzeigen

    Erstes Ergebnis: Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ff7b81f4ff8)

    Dabei markiert Xcode in der Datei Mitarbeiter die Zeile:


    Quellcode

    1. neuer_mandant.strasse = "Parkstrasse"
    Wird diese auskommentiert, dann eben jeweils die nächste.
  • Arkadiusz Paluszek schrieb:

    Mandant.xcdatamodeld
    In Persistence.swift steht:

    Quellcode

    1. container = NSPersistentContainer(name: "Mandant")
    meinst du das?

    Ja genau. Bei deiner ersten Nachricht hieß der Container noch "Mandanten". Hat sich nach der Umbenennung was geändert? Falls das immer noch nicht hilft, dann überprüfe mal die Targets des Core Data Models.

    Ansonsten mach es, wie du es selbst auch schon angedeutet hast:
    - Erstelle ein komplett neues Projekt, wähle in Xcode direkt CoreData aus, sodass der PersistenceController und das CoreData-Model automatisch erstellt werden.
    - Füge eine neue Entity "Mandant" hinzu, und dieser ein, zwei Attribute wie z.B. "Name".
    - Geh in deine ContentView, füge die viewContext als EnvironmentObject hinzu und erstelle z.B. bei Button-Klick einen neuen Mandanten (let newMandant = Mandant(...))
    - Das sollte ohne Probleme klappen. Jetzt kannst du dich Schritt für Schritt deinem Ursprungsprojekt annähern und schauen, ob der Fehler irgendwann wieder auftritt.
  • O.k., an der Stelle herzlichsten Dank für alle Beiträge die hier zuvor geschrieben wurden - Kontakt zur Datenbank ist hergestellt, Elemente können gespeichert werden.

    Aber nicht gelöscht - es scheitert bei mir noch am Wissen. Alle Tutorials die ich gefunden habe, gehen von der Darstellung der Elemente als "TableView" oder "List" aus, die integrierte Löschfunktionen besitzen. Ich habe aber auf diese Möglichkeit verzichtet und die Darstellung der Elemente selbst gebastelt:

    Quellcode: Liste_Mandanten.swift

    1. ForEach(mandanten) {mandant in
    2. let id_feld = Int(mandant.id)
    3. if(mandant.freigeschaltet) {
    4. Text(mandant.fachbereich ?? "Kein Name eingetragen")
    5. .font(.headline)
    6. .frame(width: 300, height: 30)
    7. .padding(15)
    8. .background(LinearGradient(gradient: Gradient(colors: [.red, .yellow]), startPoint: .topLeading, endPoint: .bottomTrailing))
    9. } else {
    10. HStack {
    11. Text(mandant.fachbereich ?? "Kein Name eingetragen")
    12. .font(.headline)
    13. .frame(width: 200, height: 30)
    14. .padding(15)
    15. Button(action: {
    16. self.aufgeklappte_Felder[id_feld].toggle()
    17. }, label: {
    18. Image("unlock")
    19. .resizable()
    20. .frame(width: 30, height: 30)
    21. })
    22. Button(action: {
    23. self.delete_mandant[id_feld].toggle()
    24. }, label: {
    25. Image("trash")
    26. .resizable()
    27. .frame(width: 30, height: 30)
    28. })
    29. }
    30. // Die Textfelder und der folgende Button werden nur gezeigt, wenn der Zustand des Elements von aufgeklappte_Felder in true geändert wird
    31. // Als key nutzen wir die id des MAndanten, die allerdings zuerst von Int16 (wie in der DB) ind Int konvertiert werden muss
    32. if(self.aufgeklappte_Felder[id_feld]) {
    33. TextField("Kurzbezeichnung", text: $kurzbezeichnung)
    34. .frame(width: 300)
    35. TextField("Zahlencode", text: $zahlencode)
    36. .frame(width: 300)
    37. Button(action: {
    38. mandant_freischalten(mandant: mandant, Kurzbezeichnung: self.kurzbezeichnung, Zahlencode: self.zahlencode)
    39. }, label: {
    40. Text("Schalte den Zugang frei")
    41. .font(.headline)
    42. .frame(width: 300, height: 20)
    43. .padding(10)
    44. .background(.yellow)
    45. })
    46. }
    47. // Bestätigung um einen Mandanten zu löschen
    48. if(self.delete_mandant[id_feld]) {
    49. HStack {
    50. Text("Wenn du den Mandanten aus deiner Liste löschst, kannst du ihn später wieder sehen, indem du die Liste der Mandanten erneut lädst. Dann muss allerdings jeder Mandant erneut freigeschaltet werden.")
    51. .frame(width: 200)
    52. .padding(5)
    53. Button(action: {
    54. deleteMandant(Mandant: mandant)
    55. }, label: {
    56. Image("trash")
    57. .resizable()
    58. .frame(width: 60, height: 60)
    59. })
    60. }
    61. }
    62. }
    63. }
    Alles anzeigen


    Alles schön und gut, ich scheitere nun an der Funktion deleteMandant():

    Quellcode

    1. private func deleteMandant(Mandant: NSManagedObject) {
    2. viewContext.delete(Mandant)
    3. saveContext()
    4. }

    Offensichtlich ist meine Annahme, dass der Übegebene Datensatz ein NSManagedObject sei, falsch. Die Fehlermeldung lautet "Cannot find type NSManagedObject in scope". Wie sollte die Funktion zum Löschen eines einzigen Datensatzes in meinem Fall aussehen? Danke im Voraus!
  • Arkadiusz Paluszek schrieb:

    Quellcode

    1. private func deleteMandant(mandant: Mandant) {
    2. viewContext.delete(mandant)
    3. saveContext()
    4. }
    Offensichtlich ist meine Annahme, dass der Übegebene Datensatz ein NSManagedObject sei, falsch. Die Fehlermeldung lautet "Cannot find type NSManagedObject in scope". Wie sollte die Funktion zum Löschen eines einzigen Datensatzes in meinem Fall aussehen? Danke im Voraus!


    Was soll ich sagen, die Lösung hast Du ja selbst gepostet, siehe oben…


    Wenn du as Objekt nicht hast, kannst es ja mit dem primär Schlüssel ja on the fly ermitteln und dann löschen

    Sorry, hatte etwas übersehen und deinen Code korrigiert
  • Hey!

    Die Frage ist tatsächlich etwas seltsam ;) Zum einen, da du, wie Wolf bereits sagt, die korrekte Lösung bereits zuvor gepostet hattest, und zum anderen, da die korrekte Funktion sogar automatisch von Xcode bei einem neuen Projekt mit CoreData generiert wird. Ohne dir zu nahe treten zu wollen - aber hast du mal ganz simple iOS/CoreData-Tutorials durchgemacht? Ich glaube das könnte dir wirklich helfen. Nicht nur, um die CoreData-Basics zu lernen, sondern auch um Swift-Best-Practices zu verstehen. So schreibt man z.B. die Funktionsargumente immer klein, also anstelle von deleteMandant(Mandant: NSManagedObject) sollte es deleteMandant(mandant: Mandant) heißen. Alles andere verwirrt sehr schnell und macht den Code unleserlich.

    Übrigens, zum Verständnis deiner Frage: Grundsätzlich ist deine Funktionsdeklaration deleteMandant(Mandant: NSManagedObject) gar nicht mal ganz falsch. Du musst nur CoreData importieren, da sonst, wie die Fehlermeldung sagt, NSManagedObject im Scope unbekannt ist. Dann schätze ich, dürfte die Funktion funktionieren, da Mandant ja nicht anderes als eine Subclass von NSManagedObject ist. Allerdings nimmt man üblicherweise den konkreten Typenamen, in diesem Fall also Mandant, da du sonst die Mandanten-spezifischen Informationen verlierst bzw. in der Funktion nicht darauf zugreifen kannst, obwohl das evtl. notwendig sein könnte. Zudem hilft es der Code-Leserlichkeit.

    Viele Grüße