Forståelse af iOS 13 Scene Delegate

Publiceret af donnywals den 28 oktober 201928 oktober 201928 oktober 2019

Når du opretter et nyt projekt i Xcode 11, vil du måske bemærke noget, som du ikke har set før. I stedet for kun at oprette en AppDelegate.swift-fil, en ViewController.swift, et storyboard og nogle andre filer, opretter Xcode nu en ny fil for dig; SceneDelegate.swift-filen. Hvis du aldrig har set denne fil før, kan det være ret forvirrende at forstå, hvad det er, og hvordan du skal bruge denne nye scene-delegate i din app.

I slutningen af denne uges blogindlæg vil du vide:

  • Hvad scene-delegate bruges til.
  • Hvordan du effektivt kan implementere din scene-delegate.
  • Hvorfor scene delegate er en vigtig del af iOS 13.

Lad os springe direkte ind i det, skal vi?

Undersøgelse af den nye Xcode-projektskabelon

Når du opretter et nyt Xcode-projekt, har du mulighed for at vælge, om du vil bruge SwiftUI eller Storyboards. Uanset dit valg her vil Xcode generere en ny slags projektskabelon, som du kan bygge på. Vi ser nærmere på SceneDelegate.swift– og AppDelegate.swift-filerne i næste afsnit, det vigtige for nu er, at du forstår, at Xcode har oprettet disse filer for dig.

Ud over disse to delegatfiler gør Xcode noget lidt mere subtilt. Tag et nærmere kig på din Info.plist-fil. Du bør se en ny nøgle kaldet Application Scene Manifest med et indhold, der ligner følgende billede:

Skærmbillede af Info.plist-filens scene-manifest

Skærmbillede af Info.plist-filens scene-manifest

Dette scene-manifest angiver et navn og en delegate-klasse for din scene. Bemærk, at disse egenskaber hører til et array (Application Session Role), hvilket tyder på, at du kan have flere konfigurationer i din Info.plist. En meget vigtigere nøgle, som du måske allerede har opdaget i skærmbilledet ovenfor, er Enable Multiple Windows. Denne egenskab er som standard indstillet til NO. Hvis du indstiller denne egenskab til YES, kan brugerne åbne flere vinduer af dit program på iPadOS (eller endda på macOS). At kunne køre flere vinduer af en iOS-applikation side om side er en enorm forskel fra det enkeltvinduesmiljø, vi har arbejdet med indtil nu, og muligheden for at have flere vinduer er hele grunden til, at vores app’s livscyklus nu vedligeholdes to steder i stedet for ét.

Lad os se nærmere på AppDelegate og SceneDelegate for bedre at forstå, hvordan disse to delegater arbejder sammen for at muliggøre understøttelse af flere vinduer.

Forståelse af rollerne for AppDelegate og SceneDelegate

Hvis du har bygget apps før iOS 13, kender du sikkert din AppDelegate som det ene sted, der gør stort set alt i forbindelse med din applikations lancering, forgrunds- og baggrundudførelse og lidt til. I iOS 13 har Apple flyttet nogle af AppDelegates ansvarsområder til SceneDelegate. Lad os tage et kort kig på hver af disse to filer.

AppDelegate’s ansvarsområder

Den AppDelegate er stadig det vigtigste indgangspunkt for en applikation i iOS 13. Apple kalder AppDelegate-metoder for flere livscyklusbegivenheder på applikationsniveau. I Apples standardskabelon finder du tre metoder, som Apple mener, at det er vigtigt, at du bruger:

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

Disse metoder har nogle kommentarer i dem, der faktisk beskriver, hvad de gør, så detaljeret, at du forstår, hvad de gør. Men lad os alligevel gennemgå dem hurtigt.

Når dit program lige er startet, kaldes func application(_:didFinishLaunchingWithOptions:) -> Bool. Denne metode bruges til at udføre programopsætning. I iOS 12 eller tidligere har du måske brugt denne metode til at oprette og konfigurere et UIWindow-objekt og tildelt en UIViewController-instans til vinduet for at få det vist.

Hvis din app bruger scener, er din AppDelegate ikke længere ansvarlig for at gøre dette. Da din applikation nu kan have flere vinduer eller UISceneSessions aktive, giver det ikke meget mening at administrere et enkelt vinduesobjekt i AppDelegate.

Den func application(_:configurationForConnecting:options:) -> UISceneConfiguration kaldes, når din applikation forventes at levere en ny scene eller et nyt vindue, som iOS skal vise. Bemærk, at denne metode ikke kaldes, når din app startes i første omgang, den kaldes kun for at hente og oprette nye scener. Vi vil se nærmere på oprettelse og administration af flere scener i et senere blogindlæg.

Den sidste metode i AppDelegate-skabelonen er func application(_:didDiscardSceneSessions:). Denne metode kaldes, når en bruger kasserer en scene, f.eks. ved at stryge den væk i multitaskingvinduet, eller hvis du gør det programmatisk. Hvis din app ikke kører, når brugeren gør dette, vil denne metode blive kaldt for hver kasseret scene kort efter, at func application(_:didFinishLaunchingWithOptions:) -> Bool er kaldt.

Ud over disse standardmetoder kan din AppDelegate stadig bruges til at åbne URL’er, fange hukommelsesadvarsler, registrere, hvornår din app vil blive afsluttet, om enhedens ur er ændret væsentligt, registrere, hvornår en bruger har registreret sig for fjernnotifikationer og meget mere.

Tip:
Det er vigtigt at bemærke, at hvis du i øjeblikket bruger AppDelegate til at styre din app’s statusbjælkes udseende, skal du muligvis foretage nogle ændringer i iOS 13. Flere statusbjælkerelaterede metoder er blevet forældet i iOS 13.

Nu da vi har et bedre billede af, hvad de nye ansvarsområder for din er, skal vi se nærmere på den nye SceneDelegate.

SceneDelegates ansvarsområder

Når du betragter AppDelegate som det objekt, der er ansvarlig for din applikations livscyklus, er SceneDelegate ansvarlig for det, der vises på skærmen; scenerne eller vinduerne. Før vi fortsætter, skal vi etablere noget scene-relateret ordforråd, for ikke alle udtryk betyder det, som du måske tror, de betyder.

Når du har med scener at gøre, kaldes det, der ligner et vindue for din bruger, faktisk et UIScene, som forvaltes af et UISceneSession. Så når vi henviser til vinduer, henviser vi i virkeligheden til UISceneSession-objekter. Jeg vil forsøge at holde mig til denne terminologi så meget som muligt i løbet af dette blogindlæg.

Nu, hvor vi er på samme side, skal vi se på den SceneDelegate.swift-fil, som Xcode oprettede, da den skabte vores projekt.

Der er flere metoder i SceneDelegate.swift-filen som standard:

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

Disse metoder burde virke meget velkendte for dig, hvis du er bekendt med den AppDelegate, der eksisterede før iOS 13. Lad os se på scene(_:willConnectTo:options:) først, denne metode ser sandsynligvis mindst bekendt ud for dig, og det er den første metode, der kaldes i livscyklussen for en UISceneSession.

Standardimplementeringen af scene(_:willConnectTo:options:) opretter din indledende indholdsvisning (ContentView, hvis du bruger SwiftUI), opretter en ny UIWindow, indstiller vinduets rootViewController og gør dette vindue til nøglevindue. Du kan tænke på dette vindue som det vindue, som din bruger ser. Dette er desværre ikke tilfældet. Vinduer har eksisteret siden før iOS 13, og de repræsenterer det visningsvindue, som din app opererer i. Så UISceneSession styrer det synlige vindue, som brugeren ser, UIWindow du opretter, er containervisningen for din applikation.

Ud over at oprette indledende visninger kan du bruge scene(_:willConnectTo:options:) til at genoprette din scene UI, hvis din scene tidligere har mistet forbindelsen. F.eks. fordi den er blevet sendt til baggrunden. Du kan også læse connectionOptions-objektet for at se, om din scene blev oprettet på grund af en HandOff-anmodning eller måske for at åbne en URL. Jeg vil vise dig, hvordan du gør dette senere i dette blogindlæg.

Når din scene har fået forbindelse, er den næste metode i din scenes livscyklus sceneWillEnterForeground(_:). Denne metode kaldes, når din scene vil indtage scenen. Det kan være, når din app overgår fra baggrunden til forgrunden, eller hvis den lige er ved at blive aktiv for første gang. Dernæst kaldes sceneDidBecomeActive(_:). Dette er det punkt, hvor din scene er oprettet, synlig og klar til at blive brugt.

Når din app går over i baggrunden, kaldes sceneWillResignActive(_:) og sceneDidEnterBackground(_:). Jeg vil ikke gå ind på disse metoder lige nu, da deres formål varierer for hver applikation, og kommentarerne i Xcode-skabelonen forklarer ret godt, hvornår disse metoder kaldes. Faktisk er jeg sikker på, at du selv kan finde ud af timingen af, hvornår disse metoder kaldes.

En mere interessant metode er sceneDidDisconnect(_:). Når din scene sendes til baggrunden, kan iOS beslutte at afbryde forbindelsen og rydde din scene for at frigøre ressourcer. Det betyder ikke, at din app blev dræbt eller ikke kører længere, det betyder blot, at den scene, der er overgivet til denne metode, ikke længere er aktiv og vil afbryde forbindelsen til sin session.

Bemærk, at selve sessionen ikke nødvendigvis også kasseres, iOS kan beslutte at genforbinde en scene til en scenesession til enhver tid, f.eks. når en bruger bringer en bestemt scene i forgrunden igen.

Det vigtigste i sceneDidDisconnect(_:) er at kassere alle ressourcer, som du ikke har brug for at beholde. Det kan være data, der let kan indlæses fra disken eller netværket, eller andre data, som du let kan genskabe. Det er også vigtigt at sørge for, at du beholder data, der ikke let kan genskabes, som f.eks. input, som brugeren har givet i en scene, og som brugeren forventer stadig er der, når han vender tilbage til en scene.

Tænk på en tekstbehandlingsapp, der understøtter flere scener. Hvis en bruger arbejder i én scene, og derefter har baggrund for den for at foretage nogle undersøgelser i Safari og ændre sin musik i Spotify, vil han/hun absolut forvente, at alt hans/hendes arbejde stadig eksisterer i tekstbehandlingsappen, selv om iOS måske har afbrudt tekstbehandlingsappens scene i et stykke tid. For at opnå dette skal appen bevare de nødvendige data, og den skal kode den aktuelle apptilstand i et NSUserActivity-objekt, som senere kan læses i scene(_:willConnectTo:options:), når scenen igen er tilsluttet.

Da denne arbejdsgang med tilslutning, frakobling og genforbindelse af scener kommer til at adskille de gode apps fra de gode, skal vi se på, hvordan du kan implementere genoprettelse af tilstand i din app.

Udførelse af yderligere sceneopsætning

Der er flere grunde til, at du skal udføre yderligere opsætning, når en scene bliver opsat. Du kan være nødt til at åbne en URL, håndtere en Handoff-anmodning eller gendanne tilstand. I dette afsnit vil jeg fokusere mest på genoprettelse af tilstand, da det muligvis er det mest komplekse scenarie, du kan være nødt til at håndtere.

Gendannelse af tilstand starter, når din scene bliver afbrudt, og sceneDidDisconnect(_:) bliver kaldt. På dette tidspunkt er det vigtigt, at dit program allerede har oprettet en tilstand, som kan gendannes senere. Den bedste måde at gøre dette på er at bruge NSUserActivity i din applikation. Hvis du bruger NSUserActivity til at understøtte Handoff, Siri-genveje, Spotlight-indeksering m.m., har du ikke meget ekstra arbejde at gøre. Hvis du ikke bruger NSUserActivity endnu, skal du ikke være bekymret. En simpel brugeraktivitet kan se lidt ud som følger:

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

Bemærk, at denne brugeraktivitet ikke er struktureret, som Apple anbefaler det, det er et meget nøgent eksempel, der er beregnet til at illustrere genoprettelse af tilstande. For at få en komplet vejledning om NSUserActivity anbefaler jeg, at du tager et kig på Apples dokumentation om dette emne.

Når tiden er inde til, at du skal levere en brugeraktivitet, der kan gendannes på et senere tidspunkt, kalder systemet stateRestorationActivity(for:)-metoden på din SceneDelegate. Bemærk, at denne metode ikke er en del af standardskabelonen

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

Gør du dette, tilknyttes den aktuelt aktive brugeraktivitet for en scene til scenesessionen. Husk, at når en scene afbrydes, bliver den UISceneSession, der ejer UIScene, ikke kasseret for at tillade, at sessionen kan genoprette forbindelsen til en scene. Når dette sker, kaldes scene(_:willConnectTo:options:) igen. I denne metode har du adgang til den UISceneSession, der ejer UIScene, så du kan læse sessionens stateRestorationActivity og genoprette programtilstanden efter behov:

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}

De fine detaljer i denne kode vil naturligvis variere afhængigt af dit program, men den generelle idé burde være klar.

Hvis din UISceneSession forventes at håndtere en URL, kan du inspicere connectionOptions-objektets urlContexts for at finde URL’er, som din scene skal åbne, og oplysninger om, hvordan din applikation skal gøre dette:

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

Objektet options vil indeholde oplysninger om, hvorvidt din scene skal åbne URL’en på stedet, hvilken applikation der har anmodet om at åbne denne URL og andre metadata om anmodningen.

Det grundlæggende i tilstandsgendannelse i iOS 13 med SceneDelegate er overraskende ligetil, især fordi det er bygget på NSUserActivity, hvilket betyder, at mange programmer ikke behøver at gøre alt for meget arbejde for at begynde at understøtte tilstandsgendannelse for deres scener.

Husk, at hvis du vil have understøttelse af flere scener for din app på iPadOS, er scenegenoprettelse særlig vigtig, da iOS kan afbryde og genforbinde dine scener, når de skifter fra forgrunden til baggrunden og tilbage igen. Især hvis din applikation giver en bruger mulighed for at oprette eller manipulere objekter i en scene, vil en bruger ikke forvente, at deres arbejde er væk, hvis de flytter en scene til baggrunden et øjeblik.

Sammenfattende

I dette blogindlæg har du lært en masse. Du har lært, hvilke roller AppDelegate og SceneDelegate udfylder i iOS 13, og hvordan deres livscyklus ser ud. Du ved nu, at AppDelegate er ansvarlig for at reagere på hændelser på applikationsniveau, som f.eks. appstart. SceneDelegate er ansvarlig for scene-livscyklusrelaterede hændelser. F.eks. sceneoprettelse, destruktion og genoprettelse af tilstanden for en UISceneSession. Med andre ord var hovedårsagen til, at Apple tilføjede UISceneDelegate til iOS 13, at skabe en god indgang til programmer med flere vinduer.

Efter at have lært om det grundlæggende i UISceneDelegate, så du et meget simpelt eksempel på, hvordan genoprettelse af tilstand ser ud i iOS 13 med UISceneSession og UIScene. Der er selvfølgelig meget mere at lære om, hvordan din app opfører sig, når en bruger spawner flere UISceneSession til din app, og hvordan disse scener måske skal forblive synkroniserede eller dele data.

Hvis du vil lære mere om understøttelse af flere vinduer til din iPad-app (eller din macOS-app), skal du sørge for at læse mit indlæg Tilføjelse af understøttelse af flere vinduer til din iPadOS-app. Tak for læsningen, og tøv ikke med at kontakte mig på Twitter, hvis du har spørgsmål eller feedback til mig.

Hold dig opdateret med mit ugentlige nyhedsbrev

Practical Combine

Lær alt, hvad du har brug for at vide om Combine, og hvordan du kan bruge det i dine projekter med min nye bog Practical Combine. Du får tretten kapitler, en Playground og en håndfuld eksempelprojekter, der hjælper dig med at komme i gang med Combine så hurtigt som muligt.

Bogen fås som digital download for kun 29,99 kr!

Få Practical Combine

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.