Core Data Abfrage ohne Werte

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

Aufgrund der Corona-Krise: Die Veröffentlichung von Stellenangeboten und -gesuchen ist bis 31.12.2020 kostenfrei. Das beinhaltet auch Angebote und Gesuche von und für Freischaffende und Selbstständige.

  • Core Data Abfrage ohne Werte

    Guten Morgen zusammen,

    ich habe heute eine kleine Funktion gebaut um aus der Core-Data Bank Daten abzurufen. Mein Problem ist, das Xcode einen Fehler auswirft wenn die Daten leer sind. Diese werden dabei in einem Array gespeichert und in einem Tabe View angezeigt.

    Um den Fall zu umgehen das keine Daten angegeben werden habe ich folgende Funktion gebaut:


    Quellcode

    1. func loadData() {
    2. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
    3. return
    4. }
    5. let context = appDelegate.persistentContainer.viewContext
    6. let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Recipes")
    7. do {
    8. let results = try context.fetch(request)
    9. guard results.count > 0 else {
    10. return
    11. }
    12. if let entity = results[number] as? NSManagedObject {
    13. recipe_name = entity.value(forKey: "name")as! String
    14. recipe_persons = entity.value(forKey: "persons")as! Int
    15. ingredients = entity.value(forKey: "ingredients")as! Int
    16. category = entity.value(forKey: "category")as! Int
    17. var string = String()
    18. string = entity.value(forKey: "ing\(0)") as? String ?? "fehler"
    19. for i in 0...ingredients{
    20. if string == "fehler" {
    21. let vc = AddNewIngredientViewController()
    22. vc.number = number
    23. vc.modalPresentationStyle = .automatic
    24. self.present(vc, animated: true)
    25. }else {
    26. name_array.append(entity.value(forKey: "ing\(i)")as! String)
    27. quantity_array.append(entity.value(forKey: "ingquan\(i)")as! String)
    28. }
    29. }
    30. }
    31. } catch {
    32. print(error)
    33. }
    34. }
    Alles anzeigen
    Hatte jemand bereits einmal dieses Problem und hat eine Lösung dafür?

    Vielen Dank für Eure Hilfe!

    Beste Grüße
    Tom

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Tom16092 ()

  • Es wäre hilfreich zu wissen, welchen Fehler Xcode bei einem leeren Result-Set auswirft ... ich vermute, in einer der UITableView-Datasource-Methoden? Kann es sein, dass Du dort nicht sauber auf leere Arrays reagierst oder diese nicht richtig initialisierst.

    Zwei Anmerkungen jenseits Deiner Frage: Ich finde die Verwendung mehrerer Arrays für unterschiedliche Attribute (name, quantity) irreführend und unnötig fehlerträchtig, stattdessen würde ich ein Array der NSManagedObjects verwenden. Oder besser: Ein NSFetchedResultsController macht eigentlich alles automatisch für Dich: Core Data abfragen, Caches managen, Datasource-Methoden liefern ... insbesondere in Verbindung mit asynchronen Änderungen erspart er massenhaft Arbeit.

    Mattes

    Edit: Bei dem Versuch, Deinen Code nachzuvollziehen - ich bin Swift-Analphabet - bleibt mir einiges unklar: Wie werden die CD-Attribute recipe_name etc. an die UITableView weitergegeben? Mir erschliesst sich der Zusammenhang zwischen o.g. Code und der DataSource nicht. Kann aber wie gesagt auch an mir liegen...
    Diese Seite bleibt aus technischen Gründen unbedruckt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von MyMattes ()

  • MyMattes schrieb:

    Kann es sein, dass Du dort nicht sauber auf leere Arrays reagierst oder diese nicht richtig initialisierst.
    Genau das ist meine Frage denke ich. Im angegebenen Code versuche ich das auf meine Weise...mit Fehler ?(

    Okay, den Controller für NSFetchedResults schaue ich mir mal an. Trotzdem würde es mich mal interessieren wie ihr auf leere Arrays reagiert?

    Wenn du möchtest kann ich den restlichen Code auch noch hochladen, mir ging es nur um die Reaktion auf die Arrays deswegen hatte ich den bisher weggelassen.

    Danke!
    Beste Grüße
    Tom
  • Hi Tom!

    Tom16092 schrieb:

    Wenn du möchtest kann ich den restlichen Code auch noch hochladen, mir ging es nur um die Reaktion auf die Arrays deswegen hatte ich den bisher weggelassen.
    Es geht mir nicht um „wahlloses“ Code-Geposte :) Konkret muss es Methoden des UITableViewDataSource-Protokolls geben, in denen Du z.B. die Anzahl von Zeilen und Sektionen, TableView-Zellen etc. lieferst. Diese nutzen hierfür mittelbar (z. B. per Array) oder direkt per NSFetchedResultsController die CD-Objekte. Entsprechend müsste Deine loadData-Methode oben solche Strukturen nutzen / füllen. Macht sie aber nach meinem Verständnis nicht. Wie verknüpfst Du die CD-Daten mit Deiner TableView?

    Und um auf Dein konkretes - nicht genau benanntes - Problem zurückzukommen: Welchen Fehler wirft Xcode aus?

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hi!
    Hier sind zunächst einmal die beiden Funktionen für den TableView (numberOfRowsInSection und CellForRowAt):

    Quellcode

    1. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    2. return name_array.count
    3. }
    4. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    5. let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! IngredientTableViewCell
    6. cell.descriptionLabel.text = "Menge:"
    7. cell.nameLabel.text = name_array[indexPath.row]
    8. cell.quantityLabel.text = quantity_array[indexPath.row]
    9. return cell
    10. }

    Ich habe nun die Funktion loadData() einmal in ihren Anfangs-Stand zurückgesetzt:

    Quellcode

    1. func loadData() {
    2. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
    3. return
    4. }
    5. let context = appDelegate.persistentContainer.viewContext
    6. let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Recipes")
    7. do {
    8. let results = try context.fetch(request)
    9. guard results.count > 0 else {
    10. return
    11. }
    12. if let entity = results[number] as? NSManagedObject {
    13. recipe_name = entity.value(forKey: "name")as! String
    14. recipe_persons = entity.value(forKey: "persons")as! Int
    15. ingredients = entity.value(forKey: "ingredients")as! Int
    16. category = entity.value(forKey: "category")as! Int
    17. for i in 0...ingredients{
    18. name_array.append(entity.value(forKey: "ing\(i)")as! String)
    19. quantity_array.append(entity.value(forKey: "ingquan\(i)")as! String)
    20. }
    21. }
    22. } catch {
    23. print(error)
    24. }
    25. }
    Alles anzeigen

    Die Fehlermeldung lautet wie folgt: "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"

    -> Also soweit ich das verstehe ein Fehler der damit zusammenhängt das beim entdecken der Daten kein einziger Wert gefunden wurde?

    Tom
  • Bei der Fehlermeldung brauchst Du wohl einen Swifty ... obwohl mich wundert, woher z.B. number beim Lesen eines results-Objektes kommt. Rufst Du loadData x Mal, also für jedes Rezept auf?

    Strukturell ist mir ausserdem nicht klar, warum Du im Request Rezepte abfragst, obwohl Dich in der Tabelle nur deren Zutaten interessieren. Ich vermute, diese sind in einem eigenen Entity mit einer m:n Relation zu Rezepten gespeichert? Dann frag doch direkt diese ab ... ob mit dem genanntrn Controller oder eigenständig.

    Meine Befürchtung: Eine Lösung der Optional-Fehlermeldung wird nur zu weiteren Baustellen führen. Ich habe den Eindruck, Du solltest - ohne Code zu schreiben - eine sauberes Ablaufdiagramm Deiner App erstellen.

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Number wird vom vorherigen ViewController übergeben - das ist die Zeile vom TableView vom vorherigen ViewController. Damit die App weiß welches Recipe genutzt werden soll.

    Ich frage zudem alle Daten des Rezepts ab da ich diese später wieder zuordnen möchte, also an der Richtigen Stelle... wobei, wo ich den Satz hier gerade schreiben merke ich, dass ich das wohl nochmal überdenken sollte :D

    Ich werde heute Abend mal den genauen Ablauf des Programms für die beiden ViewController erstellen. Vielleicht hilft mir das einen Überblick zu finden.

    Trotzdem einmal meine Frage: Wie würdest du den Case abfragen, also dass das Array leer ist?

    Tom
  • Tom16092 schrieb:

    Trotzdem einmal meine Frage: Wie würdest du den Case abfragen, also dass das Array leer ist?
    Ich würde vom vorigen VC das Rezept als NSManagedObject übergeben (und nicht einen Index, der später auch ungültig sein kann), dessen Attribute bzw. Zutaten direkt nutzen und mir das erneute Abfragen, Record suchen etc. komplett sparen...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Ohne mir das im Detail angesehen zu haben, bin UIKit Analphabet, hat du jede Menge an Crash-Operatoren verbaut. Schmeiss die mal alle raus, sonst kommt es dann immer zum. Crash, wenn ein Feld keinen Inhalt hat.

    hier mal ein Beispiel:
    Du: recipe_persons = entity.value(forKey: "persons")as! Int

    das ! sollte in deinen Code verboten sein. Stattdessen mit ?, ?? oder if let... arbeiten. Dann bist du auf der sicheren Seite.

    Daneben ist deine Scheife falsch, hier gehst du immer über das array hinaus...

    Beispiel: for i in 0...ingredients{

    hier als besser in 0..<ingedients oder gleich for zutat in ingrediens {

    dann hast das Problem gar nicht

    Schöne Grüsse
    Wolf
  • Hi Wolf,

    habe die Schleife mal folgendermaßen umgebaut:


    Quellcode

    1. func loadData() {
    2. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
    3. return
    4. }
    5. let context = appDelegate.persistentContainer.viewContext
    6. let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Recipes")
    7. do {
    8. let results = try context.fetch(request)
    9. guard results.count > 0 else {
    10. return
    11. }
    12. if let entity = results[number] as? NSManagedObject {
    13. recipe_name = entity.value(forKey: "name")as! String
    14. recipe_persons = entity.value(forKey: "persons")as! Int
    15. ingredients = entity.value(forKey: "ingredients")as! Int
    16. category = entity.value(forKey: "category")as! Int
    17. for i in 0...ingredients {
    18. if let data = entity.value(forKey: "ing\(i)")as? String {
    19. name_array.append(data)
    20. }
    21. if let data2 = entity.value(forKey: "ingquan\(i)")as? String {
    22. quantity_array.append(data2)
    23. }
    24. }
    25. }
    26. } catch {
    27. print(error)
    28. }
    29. }
    Alles anzeigen
    Die Reparatur and er For-Schleife hätte ich auch gerne noch umgesetzt, leider Funktioniert der Code dann aber nicht mehr. Habe diese deshalb vorerst so gelassen. Wir haben durch das if let statement nun keinen leeren Fall mehr, die leere Zeile im TableView ist so jetzt weg...

    Noch eine andere Frage: Kann man ein einzelnes Attribute in CoreData auf den default Wert zurücksetzen? Also ohne das ganze Entity zu löschen...

    Vielen Dank an Euch!

    Tom
  • Tom16092 schrieb:

    Die Reparatur and er For-Schleife hätte ich auch gerne noch umgesetzt, leider Funktioniert der Code dann aber nicht mehr. Habe diese deshalb vorerst so gelassen. Wir haben durch das if let statement nun keinen leeren Fall mehr, die leere Zeile im TableView ist so jetzt weg...
    Eigentlich wollte ich das Thema auf sich beruhen lassen, kann aber irgendwie meinen Mund nicht halten: Tom, ich würde mich wirklich von der konkreten Problematik Deiner Abfragen / Schleifen entfernen und den Lösungsansatz noch einmal überdenken: Mag ja sein, dass Du den o.g. Code "irgendwie" an's Laufen bekommst, aber eine robuste Lösung wird das schwerlich werden. Spätestens, wenn etwas die Rezeptliste verändert - z. B. ein späterer iCloud-Sync - wird number beim 2. Request plötzlich auf ein anderes Objekt zeigen und die Zutaten stimmen nicht mehr. Das Ganze ist einfach unnötig fragil...

    Überlege Dir einmal den folgenden Ansatz:
    • Die Zutaten eines Rezeptes liegen als eigene Entität mit einer many-to-many-Relation zu den Rezepten vor (wahrscheinlich hast Du dies schon so implementiert)
    • Der erste ViewController nutzt einen NSFetchedResultsController, um alle Rezepte abzufragen. Dieser sorgt automatisch für ein Cachen, verarbeitet Hintergrund-Änderungen der Daten z. B. aufgrund einer Synchronisierung und liefert ganz nebenbei alle Daten, die Du für die UITableViewDataSource benötigst.
    • Bei der Segue zu den Zutaten setzt Du das ausgewählte Rezept als Property (NSManagedObject) des zweiten ViewControllers.
    • Dieser View Controller nutzt ebenfalls einen NSFetchedResultsController, allerdings auf die Zutaten und mit einem Predicate, das die Auswahl auf Einträge des übergebenen Rezeptes einschränkt. Alternativ kannst Du auch das entsprechende Zutaten-Set des Rezeptes verwenden (verlierst dabei aber die o.g. automatische Behandlung von Hintergrund-Änderungen).
    Anschließend hast Du eine "Musterlösung", die Du immer bei vergleichbaren Aufgabenstellungen verwenden kannst, welche extrem gut skaliert und robust gegen paralleles Bearbeiten ist.

    Ich würde mir das an Deiner Stelle noch einmal sehr genau überlegen ... nun bin ich aber wirklich ruhig :)

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Tom16092 schrieb:

    Hi Wolf,

    habe die Schleife mal folgendermaßen umgebaut:


    Quellcode

    1. func loadData() {
    2. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
    3. return
    4. }
    5. let context = appDelegate.persistentContainer.viewContext
    6. let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Recipes")
    7. do {
    8. let results = try context.fetch(request)
    9. guard results.count > 0 else {
    10. return
    11. }
    12. if let entity = results[number] as? NSManagedObject {
    13. recipe_name = entity.value(forKey: "name")as! String
    14. recipe_persons = entity.value(forKey: "persons")as! Int
    15. ingredients = entity.value(forKey: "ingredients")as! Int
    16. category = entity.value(forKey: "category")as! Int
    17. for i in 0...ingredients {
    18. if let data = entity.value(forKey: "ing\(i)")as? String {
    19. name_array.append(data)
    20. }
    21. if let data2 = entity.value(forKey: "ingquan\(i)")as? String {
    22. quantity_array.append(data2)
    23. }
    24. }
    25. }
    26. } catch {
    27. print(error)
    28. }
    29. }
    Alles anzeigen
    Die Reparatur and er For-Schleife hätte ich auch gerne noch umgesetzt, leider Funktioniert der Code dann aber nicht mehr. Habe diese deshalb vorerst so gelassen. Wir haben durch das if let statement nun keinen leeren Fall mehr, die leere Zeile im TableView ist so jetzt weg...

    Noch eine andere Frage: Kann man ein einzelnes Attribute in CoreData auf den default Wert zurücksetzen? Also ohne das ganze Entity zu löschen...

    Vielen Dank an Euch!

    Tom
    Du kannst ja in das NSManaged Object einen Getter/Setter einbauen. Dann ersparst Dir das gehampel über entity.value(forkey...

    Hier kannst Du dann auch gleich mit Default werten schaffen . Da wäre ?? der passende Operator. So kannst du dann bspw. wenn kein INT gesetzt wurde, direkt 0 zurückliefern...

    und wie gesagt, du gehst hier über die schleife hinaus, da dein Schleifenzähler nicht stimmig is. .Count ist 1 basierend, for ist 0 basierend...

    hier könntest du dann folgendermassen travesieren

    for i in list.indeces() ...

    oder mit dem element zusammen
    for (e, i) in list.enumerated() ...

    Aber bau doch erst mal auf deine Datenbankkobjekte um...
  • MyMattes schrieb:

    Bei der Fehlermeldung brauchst Du wohl einen Swifty ... obwohl mich wundert, woher z.B. number beim Lesen eines results-Objektes kommt.
    Bin da. Moment! Ich lese. :)

    Joah. wie Wolf schon schrieb. Ein ! ist böse.
    Und wie Wolf schon schrieb ist indexbasiertes Rumhampeln in Schleifen böse.

    Und wie MyMattes schon schrieb ist das Übergeben eines Index auch böse.

    Das Ursprungsproblem des Ganzen ist übrigens nicht der Code, sondern das hier:

    Tom16092 schrieb:

    Core-Data Bank Daten
    CoreData ist keine Datenbank.
    CoreData nutzt war eine Datenbank, mit der hast Du aber überhaupt nix zu schaffen.

    Ich vermute auch, Du gehst von der komplett falschen Seite an das Thema ran, nämlich von der Datensicherungsseite.
    Datensicherung ist allerdings ein I/O Device (Input/Output, Eingabe/Ausgabe) und damit sowohl potentiell unsicher als auch potentiell austauschbar.
    (So wie Tastatur, Monitor, Maus, Trackpad, Drucker, USB-Stick…)

    Hinzu kommt, dass Du in diesem Setting aus mehreren Stellen auf die Datensicherung zugreifst, die theoretisch beim Wechsel von der Übersicht in die Detailseite verändert worden sein könnte und den Inhalt gar nicht mehr hat.

    Sinnvoller ist es einfach, Du übergibst das NSManagedObject (besser etwas, das eine konkrete Subklasse von NSManagedObject ist, damit Du das Gehampel mit ValueForKey sein lassen kannst und fast automagisch die Ausrufezeichenschlacht los wirst bzw. in Dein Objekt verschiebst).

    Und wenn das NSManagedObject dann auch noch ein Protokoll mit seinen Settern/Gettern implementiert kannst Du die Detailseite auch für eine Übersicht aus einer Webdatenbank oder einem JSON oder sonstwas benutzen.
    (Bei einem JSON hättest Du beispielsweise gar keine Indizes, wäre also blöd Dich darauf festzunageln)
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von Marco Feltmann ()