unable to dequeue a cell

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

  • unable to dequeue a cell

    Hallo,

    Bei der Navigation aus einer TableView (eingebettet in einen NavigationController) in eine andere TableView wird die Anwendung mit folgender Fehlermeldung beendet:

    Quellcode

    1. Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier ingredientCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'


    Ich kann mir diese Fehlermeldung nicht erklären, da dieser Code bereits funktioniert hat und ich mehrfach geprüft habe, dass der TableViewController im Storyboard auch dem entsprechenden Controller zugeordnet ist. Der Code im IngredientTableViewController ist folgender:


    Quellcode

    1. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    2. let cell = tableView.dequeueReusableCell(withIdentifier: "ingredientCell", for: indexPath)
    3. let ingredient = ingredientsManager.getIngredient(at: indexPath.row)
    4. cell.textLabel?.text = ingredient.getName()
    5. return cell
    6. }

    Und die Prototyp-Zelle heißt "ingredientCell", ich habe den Namen aus der Attribute View mehrfach kopiert und in der Methode eingefügt..

    Hätte jemand eventuell einen Tipp für mich, wo der Fehler liegen könnte?

    Vielen Dank und freundliche Grüße,
    Tazaki

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

  • Das könnte sein, also dass ich durch das Voranstellen des eingebetteten TableViewControllers vor den IngredientsTableViewController etwas unglücklich verändert habe (davor ging es meine ich noch).

    Hättest du eine Idee, wo ich nochmals nachsehen könnte?

    Kontrolliert habe ich:
    -Identity Inspector -> Custom Class -> Class -> "IngredientsTableViewController" der TableView im Storyboard
    -den Namen des Controllers Project Navigator (aber ich habe ihn durch das Dropdown Menu gewählt, deswegen dürfte es keinen Schreibfehler geben)
    -Attribute Inspector -> Table View Cell -> Identifier -> "ingredientCell" der Prototyp-Zelle
    -den Code der Methode im Controller selbst (also konkret den Identifier-String dort)

    habe ich etwas übersehen? Ich habe zum Testen noch einen weiteren TableViewController erstellt und bekomme dort den gleichen Fehler..

    /Update:
    Gerade habe ich getestet, den TableViewController wieder als Initialen ViewController zu setzen, und es hat funktioniert. Dann habe ich wohl einen Fehler in der eigentlichen Navigation..


    Das ist der Code des MainTableViewControllers:

    Quellcode

    1. import UIKit
    2. class MainTableViewController: UITableViewController {
    3. let views = [IngredientsTableViewController(), TestViewController(), TableViewController()]
    4. let viewNames = ["Ingredients", "Test", "Noch ein Test"]
    5. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    6. let cell = tableView.dequeueReusableCell(withIdentifier: "navigationCell", for: indexPath)
    7. cell.textLabel?.text = viewNames[indexPath.row]
    8. return cell
    9. }
    10. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    11. self.navigationController!.pushViewController(views[indexPath.row], animated: true)
    12. }
    13. }
    Alles anzeigen
    Der Hintergedanke ist es, die einzelnen Views im Array "views" zu sammeln und dann programmatisch die Ausgewählte dann auf den NavigationStack zu legen. Der "Test" ist ein ViewController, der in der ViewDidLoad einfach die Hintergrundfarbe ändert, das hat auch funktioniert wie es sollte, deswegen habe ich den Ablauf an sich nicht hinterfragt.. aber es ist wohl eine falsche Herangehensweise.

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

  • Ehrlich gesagt weiß ich leider nicht, wie ich das überprüfen könnte.. Im Document Outline ist die Hierarchie wie sie sein sollte, also die Prototyp-Zelle ist Subview der TableView, die wiederum Subview des IngredientsTableViewControllers ist.

    Aber da es isoliert betrachtet funktioniert (ich habe den vorangegangen Beitrag nachträglich nochmal editiert, um einen Doppel-Post zu vermeiden), muss der Fehler vom MainTableViewController ausgehen, oder?
  • Vielen Dank MCDan! :)

    Leider weiß ich noch nicht, wie ich an mein Storyboard komme, die Zeile:
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    erzeugt folgende Fehlermeldung:
    "Cannot override with a stored property 'storyboard'".

    Ich suche dazu gerade nach einer Lösung, dann teste ich Deinen Vorschlag.

    /Update: es hat funktioniert! Nochmals ganz herzlichen Dank, Du hast mir schon wieder sehr geholfen MCDan :)

    Eine letzte Frage hätte ich noch zur Optimierung, aber grundsätzlich ist es jetzt genau so, wie ich es mir vorgestellt habe.

    Der Code dazu ist folgender:

    Quellcode

    1. let sb = UIStoryboard(name: "Main", bundle: nil)
    2. lazy var ingredientsTVC = sb.instantiateViewController(withIdentifier: "ingredientsTableViewControllerID")
    3. lazy var views = [ingredientsTVC, TestViewController(), TableViewController()]
    4. let viewNames = ["Ingredients", "Test", "Noch ein Test"]
    Der Fehler oben wurde durch meine ungeschickte Namenswahl bei der Variable ("storyboard") verursacht.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Tazaki ()

  • Als mögliche Optimierung:

    Die ViewController nicht in einem Array speichern, sondern nur innerhalb von tableView(_:didSelectRowAt:) per switch Statement erzeugen und anzeigen.

    Alternativ könntest Du anstelle der ViewController nur die Identifier der ViewController in dem Array speichern und dann in tableView(_:didSelectRowAt:) die ViewController über die Identifier erzeugen und anzeigen.

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

  • Vielen Dank für Deine Vorschläge, die gefallen mir beide sehr gut! :)

    Ich werde die zweite Variante mit dem Identifier-Array umsetzen. Allgemein zu dieser Thematik hätte ich noch eine Verständnisfrage: durch den Push wird der ViewController auf den Stack gelegt und als oberstes Element dem User präsentiert, aber was genau passiert, wenn man durch die vom NavigationsController gegebene Leiste zurück navigiert?

    Also der ViewController wird vom Stack genommen, aber bleibt er noch irgendwo erhalten, um ihn schnell wieder laden zu können bei Bedarf, oder ist die Lifetime mit dem Klick auf den Zurück-Button beendet? Und unterscheidet sich dieser Vorgang in Abhängigkeit der Anwesenheit eines NavigationControllers, oder ist der Ablauf immer gleich? (Also wenn ich mich richtig erinnere, wird ohne den NavController ein Unwind aufgerufen, wodurch die View verschwindet)

    Das ist aber nicht wichtig, sondern würde mich einfach interessieren.
  • Wenn die Anzeige eines ViewController beendet wird und dieser nicht anderweitig referenziert wird, dann wird dieser aus dem Speicher entfernt.

    Die o.a. Lösung, den ViewController in tableView(_:didSelectRowAt:) zu erzeugen und anzuzeigen, führt also dazu, dass der ViewController nach der Anzeige aus dem Speicher entfernt wird und bei jedem Aufruf von tableView(_:didSelectRowAt:) ein neuer ViewController erzeugt wird.Für eine RAM-Speicher sparende App die beste Lösung.

    Alternativ könnte man den ViewController lazy erzeugen und dann beim 2. Aufruf den bereits erzeugten ViewController verwenden. Dies sollte dann zwar mehr RAM-Speicher verbrauchen, allerdings den Akku schonen, da man sich die weiteren Erzeugungen des ViewControllers spart. Bei der Entwicklung des ViewController für die 2. Variante, also der Wiederverwendung eines ViewControllers, muss diese Wiederverwendung natürlich entsprechend berücksichtigt werden, da z.B. ein viewDidLoad() natürlich nur bei der 1. Anzeige aufgerufen wird. ;)

    Keinesfalls würde ich jedoch die ViewController direkt beim Erzeugen des anzeigenden ViewControllers erzeugen, da man ja nicht weiß, ob die "Sub" ViewController überhaupt benötigt werden. Also entweder lazy oder jedes mal neu.

    Mit Swift und lazy habe ich mich noch nicht beschäftigt, aber von meinem Verständnis sollte sowohl TestViewController als auch TableViewController erzeugt werden, selbst wenn nur der IngredientsTableViewController angezeigt werden soll.

    Du kannst dies ja mal testen, indem Du eine Log-Ausgabe in der init Methode der jeweiligen ViewController verwendest. Da kannst Du dann leicht in der Console erkennen, wann diese genau erzeugt werden.

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

  • Ich danke Dir vielmals für diese ausführliche Erklärung!

    So hätte ich das auch verstanden, also dass alle Controller erzeugt werden (zwar lazy, aber da das Array als DataSource dient, wird es zwangsläufig beim ersten Laden initiiert. Das "lazy" habe ich gebraucht, weil sich Properties, die noch nicht initiiert sind, nicht aufeinander beziehen dürfen.

    1. let sb = UIStoryboard(name: "Main", bundle: nil) // hier geht es ohne "lazy" und deswegen auch mit "let", weil diese Eigenschaft unabhängig von anderen Variablen des Controllers erzeugt wird
    2. lazy var ingredientsTVC = sb.instantiateViewController(withIdentifier: "ingredientsTableViewControllerID") // für diesen ViewController benötige ich "sb", was ich aber erst verwenden darf, wenn der Controller, der diese Variablen enthält erzeugt wurde, deswegen "lazy"
    3. lazy var views = [ingredientsTVC, TestViewController(), TableViewController()] // wie bei 2., ich benötige "ingredientsTVC"


    Aber dank Deiner Hilfe brauche ich das alles nicht mehr, ich habe ein konstantes Array mit allen Identifiern, die bei Bedarf erzeugt werden :)
  • Tazaki schrieb:

    Leider weiß ich noch nicht, wie ich an mein Storyboard komme, die Zeile:
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    erzeugt folgende Fehlermeldung:
    "Cannot override with a stored property 'storyboard'".
    Wenn man sich mal die Fehlermeldung betrachtet, dann könnte man darauf kommen, dass die Zeile let storyboard = UIStory... vollkommen überflüssig ist. Jeder ViewController hat eben diese „stored property“ storyboard. Ist ein ViewController aus einem Storyboard heraus erzeugt worden, dann enthält diese Property auch eine Referenz auf dieses Storyboard. Eine weitere Instanz des Storyboards zu erzeugen ist also reine Speicherverschwendung.