Understanding the iOS 13 Scene Delegate

Published by donnywals on October 28, 2019October 28, 2019

Wanneer je een nieuw project aanmaakt in Xcode 11, valt je misschien iets op dat je nog niet eerder hebt gezien. In plaats van alleen een AppDelegate.swift-bestand, een ViewController.swift, een storyboard en enkele andere bestanden aan te maken, maakt Xcode nu een nieuw bestand voor u aan; het SceneDelegate.swift-bestand. Als u dit bestand nog nooit hebt gezien, is het misschien nogal verwarrend om te begrijpen wat het is, en hoe u deze nieuwe scènedelege in uw app moet gebruiken.

Aan het eind van de blogpost van deze week weet u:

  • Waarvoor de scènedelege wordt gebruikt.
  • Hoe u uw scènedelege effectief kunt implementeren.
  • Waarom de scènedelegeerder een belangrijk onderdeel is van iOS 13.

Laten we er meteen in springen, zullen we?

Het nieuwe Xcode-projectsjabloon

Wanneer u een nieuw Xcode-project maakt, hebt u de mogelijkheid om te kiezen of u SwiftUI of Storyboards wilt gebruiken. Ongeacht uw keuze genereert Xcode een nieuw soort projectsjabloon waarop u kunt voortbouwen. We zullen de SceneDelegate.swift en AppDelegate.swift bestanden in de volgende sectie nader bekijken, wat voor nu belangrijk is, is dat u begrijpt dat Xcode deze bestanden voor u heeft gemaakt.

Naast deze twee delegate bestanden, doet Xcode iets een beetje subtieler. Kijk eens goed naar je Info.plist bestand. U zou een nieuwe sleutel moeten zien met de naam Application Scene Manifest met een inhoud die lijkt op de volgende afbeelding:

Schermopname van het scènemanifest van het bestand Info.plist

Schermopname van het scènemanifest van het bestand Info.plist

Dit scènemanifest specificeert een naam en een delegatieklasse voor uw scène. Merk op dat deze eigenschappen deel uitmaken van een array (Application Session Role), wat suggereert dat u meerdere configuraties in uw Info.plist kunt hebben. Een veel belangrijkere sleutel die je misschien al hebt gezien in de schermafbeelding hierboven is Enable Multiple Windows. Deze eigenschap is standaard ingesteld op NO. Door deze eigenschap in te stellen op YES kunnen gebruikers meerdere vensters van uw applicatie openen op iPadOS (of zelfs op macOS). In staat zijn om meerdere vensters van een iOS applicatie naast elkaar te draaien is een enorm verschil met de enkel venster omgeving waar we tot nu toe mee hebben gewerkt, en de mogelijkheid om meerdere vensters te hebben is de hele reden dat de levenscyclus van onze app nu op twee plaatsen wordt onderhouden in plaats van op één.

Laten we de AppDelegate en SceneDelegate eens nader bekijken om beter te begrijpen hoe deze twee delegates samenwerken om ondersteuning voor meerdere vensters mogelijk te maken.

Inzicht in de rollen van AppDelegate en SceneDelegate

Als je apps hebt gebouwd vóór iOS 13, ken je waarschijnlijk je AppDelegate als de ene plaats die vrijwel alles doet met betrekking tot het starten van je applicatie, foregrounding, backgrounding en dan nog wat. In iOS 13 heeft Apple een aantal van de AppDelegate verantwoordelijkheden verplaatst naar de SceneDelegate. Laten we eens een korte blik werpen op elk van deze twee bestanden.

AppDelegate’s verantwoordelijkheden

De AppDelegate is nog steeds het belangrijkste toegangspunt voor een applicatie in iOS 13. Apple roept AppDelegate methoden voor verschillende applicatie-niveau lifecycle events. In de standaardsjabloon van Apple vindt u drie methoden die volgens Apple belangrijk zijn om te gebruiken:

  • func application(_:didFinishLaunchingWithOptions:) -> Bool
  • func application(_:configurationForConnecting:options:) -> UISceneConfiguration
  • func application(_:didDiscardSceneSessions:)

Deze methoden hebben enig commentaar in zich dat feitelijk beschrijft wat ze doen in voldoende detail om te begrijpen wat ze doen. Maar laten we ze toch even snel doornemen.

Wanneer uw applicatie net is gestart, wordt func application(_:didFinishLaunchingWithOptions:) -> Bool aangeroepen. Deze methode wordt gebruikt om de setup van de applicatie uit te voeren. In iOS 12 of eerder heb je deze methode misschien gebruikt om een UIWindow-object te maken en te configureren en een UIViewController-instantie aan het venster toe te wijzen om het te laten verschijnen.

Als je app scènes gebruikt, is je AppDelegate niet langer verantwoordelijk om dit te doen. Aangezien uw toepassing nu meerdere vensters, of UISceneSessions actief kan hebben, heeft het niet veel zin om een enkel-venster object in de AppDelegate te beheren.

De func application(_:configurationForConnecting:options:) -> UISceneConfiguration wordt aangeroepen wanneer uw toepassing wordt verwacht een nieuwe scène, of venster voor iOS te leveren om weer te geven. Merk op dat deze methode niet wordt aangeroepen wanneer uw app initieel opstart, het wordt alleen aangeroepen om nieuwe scènes te verkrijgen en te creëren. We zullen dieper ingaan op het maken en beheren van meerdere scènes in een latere blog post.

De laatste methode in de AppDelegate template is func application(_:didDiscardSceneSessions:). Deze methode wordt aangeroepen wanneer een gebruiker een scène weggooit, bijvoorbeeld door deze weg te vegen in het multitasking-venster of wanneer u dit programmatisch doet. Als uw app niet draait wanneer de gebruiker dit doet, wordt deze methode voor elke weggegooide scène aangeroepen kort nadat func application(_:didFinishLaunchingWithOptions:) -> Bool is aangeroepen.

Naast deze standaardmethoden kan uw AppDelegate nog worden gebruikt om URL’s te openen, geheugenwaarschuwingen op te vangen, te detecteren wanneer uw app wordt beëindigd, of de klok van het apparaat aanzienlijk is gewijzigd, te detecteren wanneer een gebruiker zich heeft geregistreerd voor meldingen op afstand en nog veel meer.

Tip:
Het is belangrijk om op te merken dat als u momenteel AppDelegate gebruikt om het uiterlijk van de statusbalk van uw app te beheren, u in iOS 13 mogelijk enkele wijzigingen zult moeten doorvoeren. Verschillende statusbalkgerelateerde methoden zijn in iOS 13 afgeschreven.

Nu we een beter beeld hebben van wat de nieuwe verantwoordelijkheden van uw AppDelegate zijn, laten we eens kijken naar de nieuwe SceneDelegate.

SceneDelegate’s verantwoordelijkheden

Wanneer u de AppDelegate beschouwt als het object dat verantwoordelijk is voor de levenscyclus van uw applicatie, is de SceneDelegate verantwoordelijk voor wat er op het scherm wordt getoond; de scènes of vensters. Voordat we verder gaan, laten we wat scène-gerelateerde woordenschat vaststellen, omdat niet elke term betekent wat u denkt dat het betekent.

Wanneer u te maken heeft met scènes, wat er voor uw gebruiker uitziet als een venster, wordt eigenlijk een UIScene genoemd, die wordt beheerd door een UISceneSession. Dus als we het over vensters hebben, hebben we het eigenlijk over UISceneSession objecten. Ik zal proberen me zoveel mogelijk aan deze terminologie te houden in de loop van deze blog post.

Nu we op dezelfde pagina zitten, laten we eens kijken naar het SceneDelegate.swift bestand dat Xcode heeft gemaakt toen het ons project maakte.

Er staan standaard verschillende methoden in het SceneDelegate.swift-bestand:

  • scene(_:willConnectTo:options:)
  • sceneDidDisconnect(_:)
  • sceneDidBecomeActive(_:)
  • sceneWillResignActive(_:)
  • sceneWillEnterForeground(_:)
  • sceneDidEnterBackground(_:)

Deze methoden zouden u heel bekend voor moeten komen als u bekend bent met de AppDelegate die vóór iOS 13 bestonden. Laten we eerst eens kijken naar scene(_:willConnectTo:options:), deze methode komt u waarschijnlijk het minst bekend voor en is de eerste methode die wordt aangeroepen in de levenscyclus van een UISceneSession.

De standaard implementatie van scene(_:willConnectTo:options:) creëert uw initiële inhoudsweergave (ContentView als u SwiftUI gebruikt), creëert een nieuwe UIWindow, stelt de rootViewController van het venster in en maakt dit venster tot het hoofdvenster. Je zou kunnen denken aan dit venster als het venster dat je gebruiker ziet. Dit is, helaas, niet het geval. Vensters zijn er al van voor iOS 13 en ze vertegenwoordigen de viewport waarin je app opereert. Dus, de UISceneSession regelt het zichtbare venster dat de gebruiker ziet, de UIWindow die u maakt is de container view voor uw applicatie.

Naast het instellen van initiële views, kunt u scene(_:willConnectTo:options:) gebruiken om uw scene UI te herstellen in het geval uw scene in het verleden is verbroken. Bijvoorbeeld, omdat het naar de achtergrond werd gestuurd. U kunt ook het connectionOptions object lezen om te zien of uw scene is gemaakt als gevolg van een HandOff verzoek of misschien om een URL te openen. Ik zal u laten zien hoe dit te doen later in deze blog post.

Zodra uw scène is aangesloten, is de volgende methode in de levenscyclus van uw scène sceneWillEnterForeground(_:). Deze methode wordt aangeroepen wanneer uw scène het podium zal nemen. Dit kan zijn wanneer uw app overgaat van de achtergrond naar de voorgrond, of als het net actief wordt voor de eerste keer. Vervolgens wordt sceneDidBecomeActive(_:) aangeroepen. Dit is het punt waarop uw scène is opgezet, zichtbaar is en klaar om te worden gebruikt.

Wanneer uw app naar de achtergrond gaat, worden sceneWillResignActive(_:) en sceneDidEnterBackground(_:) aangeroepen. Ik ga nu niet in op deze methodes omdat hun doel verschilt voor elke applicatie, en de opmerkingen in het Xcode sjabloon doen vrij goed werk om uit te leggen wanneer deze methodes worden aangeroepen. Eigenlijk weet ik zeker dat je zelf kunt achterhalen wanneer deze methoden worden aangeroepen.

Een interessantere methode is sceneDidDisconnect(_:). Wanneer uw scène naar de achtergrond wordt gestuurd, kan iOS besluiten om de verbinding te verbreken en uw scène te wissen om bronnen vrij te maken. Dit betekent niet dat uw app is gedood of niet meer draait, het betekent gewoon dat de scène die aan deze methode is doorgegeven niet meer actief is en wordt losgekoppeld van zijn sessie.

Merk op dat de sessie zelf niet noodzakelijkerwijs ook wordt weggegooid, iOS kan op elk gewenst moment besluiten om een scène opnieuw te koppelen aan een scène-sessie, bijvoorbeeld wanneer een gebruiker een bepaalde scène weer naar de voorgrond brengt.

Het belangrijkste om te doen in sceneDidDisconnect(_:) is om alle bronnen weg te gooien die je niet nodig hebt om in de buurt te houden. Dit kunnen gegevens zijn die gemakkelijk van schijf of het netwerk kunnen worden geladen of andere gegevens die je gemakkelijk opnieuw kunt maken. Het is ook belangrijk om ervoor te zorgen dat u alle gegevens behoudt die niet gemakkelijk opnieuw kunnen worden gemaakt, zoals bijvoorbeeld alle invoer die de gebruiker in een scène heeft gegeven en waarvan hij zou verwachten dat deze er nog steeds is wanneer hij terugkeert naar een scène.

Overweeg een tekstverwerkingsapp die meerdere scènes ondersteunt. Als een gebruiker in één scène werkt en vervolgens naar de achtergrond gaat om wat onderzoek te doen in Safari en zijn muziek in Spotify te wijzigen, zou hij absoluut verwachten dat al zijn werk nog steeds bestaat in de tekstverwerkingsapp, ook al heeft iOS de scène van de tekstverwerkingsapp misschien een tijdje losgekoppeld. Om dit te bereiken, moet de app de vereiste gegevens bewaren, en moet hij de huidige app-status coderen in een NSUserActivity-object dat later kan worden gelezen in scene(_:willConnectTo:options:) wanneer de scène opnieuw wordt verbonden.

Omdat deze workflow van verbinden, loskoppelen en opnieuw verbinden van scènes de goede apps van de geweldige gaat scheiden, laten we eens kijken hoe u toestandsherstel in uw app kunt implementeren.

Extra scène-instelling uitvoeren

Er zijn verschillende redenen waarom u extra instellingen moet uitvoeren wanneer een scène wordt ingesteld. Je moet bijvoorbeeld een URL openen, een Handoff verzoek afhandelen of de status herstellen. In deze sectie zal ik me vooral richten op het herstellen van de status, omdat dat mogelijk het meest complexe scenario is dat je moet afhandelen.

Het herstellen van de status begint wanneer je scene wordt losgekoppeld en sceneDidDisconnect(_:) wordt aangeroepen. Op dit punt is het belangrijk dat uw toepassing al een toestand heeft opgezet die later kan worden hersteld. De beste manier om dit te doen is door NSUserActivity te gebruiken in uw applicatie. Als je NSUserActivity gebruikt om Handoff, Siri Shortcuts, Spotlight indexering en meer te ondersteunen, heb je niet veel extra werk te doen. Als je NSUserActivity nog niet gebruikt, maak je dan geen zorgen. Een eenvoudige gebruikersactiviteit zou er ongeveer als volgt uit kunnen zien:

let activity = NSUserActivity(activityType: "com.donnywals.DocumentEdit")activity.userInfo = 

Merk op dat deze gebruikersactiviteit niet is gestructureerd zoals Apple het aanbeveelt, het is een heel kaal voorbeeld bedoeld om statusherstel te illustreren. Voor een volledige handleiding over NSUserActivity, raad ik u aan om de documentatie van Apple over dit onderwerp te bekijken.

Wanneer de tijd is gekomen om een gebruikersactiviteit op te geven die op een later tijdstip kan worden hersteld, roept het systeem de methode stateRestorationActivity(for:) op uw SceneDelegate aan. Merk op dat deze methode geen deel uitmaakt van het standaard sjabloon

func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { return scene.userActivity}

Door dit te doen wordt de momenteel actieve gebruikersactiviteit voor een scene geassocieerd met de scene sessie. Onthoud dat wanneer een scene wordt losgekoppeld, de UISceneSession die eigenaar is van de UIScene niet wordt weggegooid om de sessie opnieuw te laten verbinden met een scene. Wanneer dit gebeurt, wordt scene(_:willConnectTo:options:) opnieuw aangeroepen. In deze methode heeft u toegang tot de UISceneSession die eigenaar is van de UIScene, zodat u de stateRestorationActivity van de sessie kunt lezen en de toestand van de toepassing kunt herstellen als dat nodig is:

if let activity = session.stateRestorationActivity, activity.activityType == "com.donnywals.DocumentEdit", let documentId = activity.userInfo as? String { // find document by ID // create document viewcontroller and present it}

Natuurlijk zullen de fijne details van deze code variëren afhankelijk van uw toepassing, maar het algemene idee zou duidelijk moeten zijn.

Als van uw UISceneSession wordt verwacht dat deze een URL afhandelt, kunt u de urlContexts van het connectionOptions-object inspecteren om URL’s te vinden die uw scène moet openen en informatie over hoe uw toepassing dit moet doen:

for urlContext in connectionOptions.urlContexts { let url = urlContext.url let options = urlContext.options // handle url and options as needed}

Het options-object zal informatie bevatten over of uw scène de URL ter plaatse moet openen, welke toepassing heeft gevraagd deze URL te openen en andere metadata over het verzoek.

De basis van toestandherstel in iOS 13 met de SceneDelegate is verrassend eenvoudig, vooral omdat het is gebouwd op NSUserActivity, wat betekent dat veel toepassingen niet al te veel werk hoeven te doen om te beginnen met het ondersteunen van toestandherstel voor hun scènes.

Bedenk dat als je ondersteuning wilt hebben voor meerdere scènes voor je app op iPadOS, scèneherstel vooral belangrijk is omdat iOS je scènes zou kunnen ontkoppelen en opnieuw verbinden wanneer ze overschakelen van de voorgrond naar de achtergrond en weer terug. Vooral als uw toepassing een gebruiker toestaat om objecten in een scène te maken of te manipuleren, zou een gebruiker niet verwachten dat hun werk weg is als ze een scène even naar de achtergrond verplaatsen.

In samenvatting

In deze blogpost hebt u veel geleerd. U hebt geleerd welke rollen de AppDelegate en SceneDelegate vervullen in iOS 13 en hoe hun levenscycli eruit zien. U weet nu dat de AppDelegate verantwoordelijk is voor het reageren op gebeurtenissen op applicatieniveau, zoals bijvoorbeeld het starten van een app. De SceneDelegate is verantwoordelijk voor scene lifecycle gerelateerde events. Bijvoorbeeld, scene creatie, vernietiging en herstel van de toestand van een UISceneSession. Met andere woorden, de belangrijkste reden voor Apple om UISceneDelegate toe te voegen aan iOS 13 was om een goede ingang te creëren voor multi-windowed applicaties.

Na het leren over de basis van UISceneDelegate, zag je een heel eenvoudig voorbeeld van hoe toestandsherstel eruit ziet in iOS 13 met UISceneSession en UIScene. Natuurlijk is er nog veel meer te leren over hoe uw app zich gedraagt wanneer een gebruiker meerdere UISceneSessions voor uw app spawnt, en hoe deze scènes mogelijk synchroon moeten blijven of gegevens moeten delen.

Als u meer wilt weten over de ondersteuning van meerdere vensters voor uw iPad-app (of uw macOS-app), moet u zeker mijn post Toevoegen van ondersteuning voor meerdere vensters aan uw iPadOS-app bekijken. Bedankt voor het lezen, en aarzel niet om contact met me op te nemen via Twitter als je vragen of feedback voor me hebt.

Blijf op de hoogte met mijn wekelijkse nieuwsbrief

Practical Combine

Leer alles wat je moet weten over Combine en hoe je het in je projecten kunt gebruiken met mijn nieuwe boek Practical Combine. Je krijgt dertien hoofdstukken, een Playground en een handvol voorbeeldprojecten om je te helpen zo snel mogelijk met Combine aan de slag te gaan.

Het boek is verkrijgbaar als digitale download voor slechts $29,99!

Get Practical Combine

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.