State-Restoration von UISplitViewController mit CoreData

  • State-Restoration von UISplitViewController mit CoreData

    Leute,

    ich drehe bald am Rad und brauche echt Eure Hilfe!

    Sorry schon vorab für das lange Posting: Ich versuche, vorab alle relevanten Infos zu geben, im Zweifelsfall bitte nachfragen...

    Ich möchte einer iOS-App State-Restoration beibringen. Bisher habe ich dies (in einer anderen App) nur mit eigenen Methoden realisiert und möchte nun den "Apple-Weg" gehen. Die Applikation verwendet CoreData, basiert auf dem Xcode-Template "Master- / Detail-View mit CoreData" und ihre Struktur sieht wie folgt aus:

    • SplitViewController
      • NavigationController
        • MasterViewController (UITableView 1)
      • NavigationController
        • DetailViewController 1 (UIPageView)
          • CustomViewController 1
          • CustomViewController 2
      • NavigationController
        • DetailViewController 2 (UITableView 2)


    Zwecks State-Restoration habe ich folgendes bisher gemacht:
    1. Opt-in im Application-Delegate für shouldSave- und -RestoreApplicationState
    2. Vergabe von RestorationIDs für alle View-Controller und die UITableView 1 (alle im Storyboard)
    3. Implementierung des <UIDataSourceModelAssociation>-Protokolls in der DataSource der UITableView 1
    4. Logausgaben in application:viewControllerWithRestorationIdentifierPath:coder: im AppDelegate (wird für alle beteiligten ViewController aufgerufen)
    5. Logausgaben in encodeRestorableStateWithCoder: und decodeRestorableStateWithCoder: aller View-Controller (werden aufgerufen)
    Die State-Restoration der UITableView inkl. CoreData funktioniert. Wenn ich jedoch durch Auswahl einer Zeile entweder in die DetailView 1 oder 2 navigiert habe, erhalte ich bei der Wiederherstellung den folgenden Fehler, den ich nicht eingegrenzt bekomme:

    Quellcode

    1. -[UINavigationController setManagedObjectContext:]: unrecognized selector sent to instance 0x7ff30186b600
    2. *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setManagedObjectContext:]: unrecognized selector sent to instance 0x7ff30186b600'
    Mir ist vollkommen unklar, warum diese Methode an den NavigationController geschickt wird. Leider ist die gesamte Dokumentation inkl. Google-Ergebnissen zum Thema State-Restoration mit CoreData eher mau.

    Wenn von Euch nicht eine super Idee oder auch nur ein Fingerzeig in die richtige Richtung (oder zu einer guten Dokumentation) kommt, werde ich die App noch einmal vom Template an Stück für Stück nachbauen und bei jedem Schritt die Wiederherstellung testen... ;(

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Interessant ist hier die Frage, wie genau Du auf den ManagedObjectContext in den jeweiligen DetailViews während der Wiederherstellung zugreifst.
    «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
  • Marco Feltmann schrieb:

    Interessant ist hier die Frage, wie genau Du auf den ManagedObjectContext in den jeweiligen DetailViews während der Wiederherstellung zugreifst.
    Das ist eine sehr gute Frage, Marco ;)

    Bisher wird nach Auswahl einer Zeile das ManagedObject in den prepareForSegue-Methoden in einem Property des Ziel-Controllers übergeben.

    Da ich noch keine Idee habe, wie ich dies in der Restauration bewerkstelligen soll, passiert dort bisher nichts, das Property ist also nil. Mir ist klar, dass so der DetailViewController keine Daten zum Anzeigen hat, aber (1.) handelt es sich um ein ManagedObject und keinen MOContext und (2.) ist der NavigationController doch nicht beteiligt ... oder ich verstehe den Ablauf der Restauration nicht richtig.

    Mein Alternativplan ist z. Z., statt des MO nur die URI des Objektes zu übergeben bzw. zwecks Restore zu speichern. Damit fragt dann jeder VC selber die Daten ab und kann das auch in der Wiederherstellungs-Methode machen.

    Mattes

    P.S.: Nicht wundern, wenn ich nun verzögert reagiere: Morgen ist Heimreise nach einer Woche DK, und die Sachen sind schon gepackt. Schade :(
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • In dem Fall könnte ein Exception Breakpoint mit aussagekräftigem Stacktrace weiterhelfen.

    Kannst Du gegebenenfalls ein minimalisiertes Testprojekt hochladen?

    Idealerweise in einem offen lesbaren SCM Repository.
    Noch idealerweise auf Grund Faulheit Git oder Mercurial. ^^
    «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
  • Marco Feltmann schrieb:

    Kannst Du gegebenenfalls ein minimalisiertes Testprojekt hochladen?
    Ich werde mal sehen, ob ich den Fall die Tage ausgehend vom reinen Xcode-Template reproduzieren kann ... wenn nicht, versuche ich mich wahrscheinlich am o. g. Plan B (Übergabe der MO-URI).

    Nutzt den einer von Euch State Restoration für eine Master- / DetailView-Application mit Storyboard und CoreData?

    Mattes

    P.S.: Käme dann als Public-Repository auf Github ;)
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Is ja gut, ich schau mal. :P
    Wie hast Du die beiden Detail–Navigation–Controller verdrahtet? Segues direkt an den Master TV Controller?
    «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 1 mal editiert, zuletzt von Marco Feltmann ()

  • Via "performSegue" aus dem didSelectRow der TableView. Du erinnerst Dich vielleicht an mein anderes Problem, Segues abhängig vom Edit-Mode der TableView auszuführen...

    Wäre natürlich übel, wenn die implizite State Restoration nur mit den "Push (Detail)" Seagues gehen würde: Diese funktioniert nämlich bei o. g. Anforderung nicht.

    So, ich bin jetzt weg: Morgen wird's früh und den ganzen Tag nur gefahren.

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Maaan, man sollte vorher lesen…

    statt des MO nur die URI des Objektes zu übergeben
    NSManagedObject ist weder NSCopying noch NSCoding Compliant.

    Demnach passiert nix außer ne Exception, wenn Du das Ding durch den Keyed(Un)Archiver jagen willst…

    Egal, was den Fehler betrifft:
    Mutmaßlich gibt Deine application:viewControllerWithRestorationIdentifierPath:coder: irgendwo das Falsche zurück.

    Vielleicht ein [[rootViewController childViewControllers] lastObject]; anstelle eines [[[rootViewController childViewControllers] lastObject] topViewController]; irgendwo?
    «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
  • Marco Feltmann schrieb:

    Maaan, man sollte vorher lesen…

    statt des MO nur die URI des Objektes zu übergeben
    NSManagedObject ist weder NSCopying noch NSCoding Compliant.
    Demnach passiert nix außer ne Exception, wenn Du das Ding durch den Keyed(Un)Archiver jagen willst
    Das kann man im Delegate von (Un)Archiver abhandeln.

    Chris
    Man macht einfach solange irgendwelche Dinge, bis man tot ist.
    Und dann bekommen die anderen Kuchen.
  • Nach 10h drüber Schlafen kam eigentlich schon die Erleuchtung. Werde ich heute Abend mal ausprobieren.

    Die restorationClass Property beider Details auf die Klasse des TableView Controllers setzen, der dann im Restoration Process das entsprechende MO übergibt.

    Wie ich dann auf dem Phone dem UINavigationController beigepult bekomme seine History zu behalten sehen wir dann auch noch. ;)

    Chris Ja, mag sein. Ändert aber nix, dass man das MO in dem Schritt nicht für die Restoration aufbearbeiten kann. :)
    «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
  • Marco Feltmann schrieb:

    Nach 10h drüber Schlafen kam eigentlich schon die Erleuchtung. Werde ich heute Abend mal ausprobieren.
    Mach' Dir nicht zu viel Stress ;) Ich bin nach 11h unterwegs nur noch kaputt, aber werde die Tage mal weiter forschen ... wenn nur nicht am Montag wieder der Job riefe (App-Development ist eben nur ein Hobby für mich).

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Okay, ich gebe nun auf. Bei dem Versuch, die o. g. Konstellation in einem minimalen Beispiel-Projekt nachzustellen erhalte ich reproduzierbar einen etwas anderen Fehler. Folgendes Vorgehen:
    1. Neues Xcode-Projekt auf Basis der Master- / Detail-View Anwendung mit CoreData in Xcode Version 7.0.1 (7A1001)
    2. Opt-In für State Restoration im App-Delegate
    3. Setzen von Restoration-IDs in allen Navigation- und View-Controllern
    4. Crash beim Restore mit der Meldung "Exception occurred restoring state +entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Event'"
    Dieser Crash erfolgt, nachdem die Wiederherstellung des DetailViewControllers vom AppDelegate geloggt wurde, aber vor Aufruf seiner decodeRestorableStateWithCoder:-Methode.

    Ich vermute hierbei eine ähnliche Ursache wie beim ursprünglichen Problem, nämlich ein nicht korrekt wiederhergestellter ManagedObjectContext. Dabei wird mir offensichtlich, dass ich das ganze Prinzip der Restauration (insbesondere in Zusammenhang mit Storyboard und CoreData) nicht wirklich verstehe:

    Selbst wenn ich im prepareForSegue: gar kein ManagedObject übergebe (sondern nur eine Konstant ausgebe) und das korrespondierende Property auskommentiere, wird bei der Wiederherstellung besagter Context referenziert ... und führt zum Crash. Mir ist einfach nicht klar, was "unter der Haube" genau abläuft, welche Objekte ich en- / decoden muss und welche nicht. Es sind einfach zu viele Fragezeichen, zu denen ich keine Beschreibung gefunden habe.

    Ich werde wahrscheinlich (mehr oder minder frustriert) einen andern Ansatz verfolgen:

    Marco Feltmann schrieb:

    Die restorationClass Property beider Details auf die Klasse des TableView Controllers setzen, der dann im Restoration Process das entsprechende MO übergibt.
    Momentan denke ich darüber nach, in der Wiederherstellung alle beteiligten ViewController im AppDelegate durch die Methode application:viewControllerWithRestorationIdentifierPath:coder: anzulegen und deren Properties wiederherzustellen. Für Objekte, die kein <NSCoding> unterstützen, werde ich mir was einfallen lassen müssen. Der Ansatz erscheint mir zwar unsauber, weil dieser den AppDelegate mit eigentlich unsinnigen Abhängigkeiten überfrachtet, aber ich weiss mir keine Rat mehr.

    Wenn es interessiert anbei das Beispiel-Projekt inkl. Git-Historie und die aktuelle CrashLog mit Call-Stack...

    Mattes

    Edit: Beispiel-Code mit fehlerhafter / unvollständiger Implementierung entfernt...
    Dateien
    • Crashlog.txt

      (4,15 kB, 244 mal heruntergeladen, zuletzt: )
    Diese Seite bleibt aus technischen Gründen unbedruckt.

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

  • Nuja, Du fütterst Deinen View Controller mit Abhängigkeiten, die er doof findet.

    Vermutlich machst Du genau dieselben Fehler wie ich. ;)

    Dein Crash dürfte im Master View Controller beim Heraussuchen des Objektes am Indexpfad im FetchedResults Controller liegen.
    Das <UIDataSourceModelAssociation> Protokoll führt sich noch vor dem -viewDidLoad: des View Controllers direkt auf dem TableView aus.
    Hier musst Du den ManagedObject Context bei der Rückgabe des Controllers im AppDelegate setzen.
    (Persistieren des MOC hilft nicht, da der Persistent Store Coordinater ebenfalls kein NSCoding kann und dann entsprechend hinten raus fehlt.)
    «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
  • Marco Feltmann schrieb:



    Hier musst Du den ManagedObject Context bei der Rückgabe des Controllers im AppDelegate setzen.
    Danke für die aufbauenden Worte ;)

    Ich werde das o. g. beim Implementieren der Delegate-Methode für's Restore berücksichtigen.

    Schade, dass sich nirgendwo ein Beispielcode zu diesem Thema zu geben scheint. Entweder ist es vollkommen simpel (und ich einfach zu blöd) oder es scheint keiner zu wirklich zu verstehen. Komisch...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Beispielcode? Bitteschön.
    github.com/mfeltmann/iPadStateRestoration

    Wie dem README zu entnehmen läuft das super auf dem iPad, nicht auf dem iPhone.
    Liegt wohl in der unterschiedlichen Implementierung des UISplitViewController für iPhone und iPad in iOS 8.0

    Beim Phone liegen einige Objektbäume der ViewController halt noch nicht vor, die das iPad schon vernünftig geladen hat.
    Ich habe da nur den Crash rausoptimiert (AppDelegate.m:38-41), der beim iPhone halt diese Exception warf:
    -[UINavigationController setManagedObjectContext:]: unrecognized selector sent to instance 0x…
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setManagedObjectContext:]: unrecognized selector sent to instance 0x…'

    (Waren gut investierte ~5 Stunden zuzüglich drüber schlafen, wieder schön Erfahrung angesammelt. Manchmal ist es wirklich hilfreich und spannend, sich terriermäßig in ein Problem zu verbeißen.)
    «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
  • Lieber so als den letalen Männerschnupfen!
    Mal sehen, wie lang die Reinkarnationszeit diesmal ist. ;)

    MyMattes schrieb:

    Der Ansatz erscheint mir zwar unsauber, weil dieser den AppDelegate mit eigentlich unsinnigen Abhängigkeiten überfrachtet, aber ich weiss mir keine Rat mehr.
    Nun ja, das App Delegate kennt ja im Template schon sowohl sein Master Table View als auch seinen Detail Table View.
    Du baust da kaum weitere Abhängigkeiten ein. ;)
    «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
  • Kurz: Et lüppt!

    Marco Feltmann schrieb:


    (Waren gut investierte ~5 Stunden zuzüglich drüber schlafen, wieder schön Erfahrung angesammelt. Manchmal ist es wirklich hilfreich und spannend, sich terriermäßig in ein Problem zu verbeißen.)
    Mir hat das Thema auch absolut keine Ruhe gelassen, ging mir die ganze Zeit durch den Kopf. Heute Abend bin ich Deinen Code durchgegangen und habe noch einmal gedanklich alles auf Anfang gesetzt. Nun habe ich die Restoration auf iPhone und iPad am Laufen, auch wenn es noch Verbesserungspotential und eine kleine (?) Baustelle gibt. Die beiden folgenden Punkte waren entscheidend:
    • Gestern hatte ich in einem Blog den folgenden Satz gelesen: "as a general guideline you will want to perform most application initialisation before state restoration takes place so that things such as the data model are available when the view controllers are restored" ... Stimmt, das macht Sinn. Es ging um die neue Methode "application:willFinishLaunchingWithOptions:" des AppDelegates. Nachdem ich die Initialisierungen der ViewController aus dem "...didFinish.." hierher verschoben hatte, war der Crash weg und der ViewController-Stack wird sauber wiederhergestellt. Eine Rückgabe der ViewController im AppDelegate (application:viewControllerWithRestorationIdentifierPath:coder:) ist dafür nicht notwendig. Nur das ManagedObject bleibt (wahrscheinlich mangels <NSCoding>-Unterstützung leer.
    • Also Alternativplan von oben gezogen: Ich übergebe nicht das MO zwischen den ViewControllern, sondern nur den Identifier als NSString. Dieser kann ganz simpel im encode- / decodeWithCoder: wiederhergestellt werden, und jeder ViewController holt sich selber das referenzierte Objekt aus dem CoreData-Store (und setzt dabei auch seinen ManagedObjectContext). Okay, das sind 2-3 Abfragen mehr als notwendig, aber damit kann ich leben.
    Jetzt muss ich dem UIPageViewController nur noch beibringen, auf die zuletzt angezeigte Seite zu springen. Nachdem was ich bisher gesehen habe, lässt sich diese nur über eine seine Delegate-Methoden herausfinden, aber das bekomme ich auch noch hin.

    Ein fettes Danke für's Mitdenken, den Beispielcode und die moralische Unterstützung :D

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Kollegen, danke für diesen Thread hier. Ich hatte ein paar Probleme mit State Restoration, aber letztendlich lag es an:


    MyMattes schrieb:


    Es ging um die neue Methode "application:willFinishLaunchingWithOptions:" des AppDelegates. Nachdem ich die Initialisierungen der ViewController aus dem "...didFinish.." hierher verschoben hatte, war der Crash weg und der ViewController-Stack wird sauber wiederhergestellt.
    Also, willFinish und didFinish vertauscht und nun hat der View Controller den managedObjectContext wie immer.