Comprendere il delegato di scena di iOS 13

Pubblicato da donnywals il 28 ottobre 2019Ottobre 2019

Quando crei un nuovo progetto in Xcode 11, potresti notare qualcosa che non hai visto prima. Invece di creare solo un file AppDelegate.swift, un ViewController.swift, uno storyboard e alcuni altri file, Xcode ora crea un nuovo file per te; il file SceneDelegate.swift. Se non avete mai visto questo file prima, potrebbe essere abbastanza confuso capire cos’è, e come dovreste usare questo nuovo delegato di scena nella vostra app.

Per la fine del post di questa settimana saprete:

  • A cosa serve il delegato di scena.
  • Come potete implementare efficacemente il vostro delegato di scena.
  • Perché il delegato di scena è una parte importante di iOS 13.

Partiamo subito, che ne dite?

Esaminare il nuovo modello di progetto Xcode

Ogni volta che create un nuovo progetto Xcode, avete la possibilità di scegliere se volete usare SwiftUI o Storyboard. Indipendentemente dalla tua scelta, Xcode genererà un nuovo tipo di modello di progetto su cui costruire. Daremo un’occhiata più da vicino ai file SceneDelegate.swift e AppDelegate.swift nella prossima sezione, ciò che è importante per ora è che tu capisca che Xcode ha creato questi file per te.

Oltre a questi due file delegati, Xcode fa qualcosa di un po’ più sottile. Date un’occhiata da vicino al vostro file Info.plist. Dovresti vedere una nuova chiave chiamata Application Scene Manifest con un contenuto simile alla seguente immagine:

Screenshot del manifesto di scena del file Info.plist

Screenshot del manifesto di scena del file Info.plist

Questo manifesto di scena specifica un nome e una classe di delegato per la tua scena. Nota che queste proprietà appartengono a un array (Application Session Role), suggerendo che puoi avere più configurazioni nel tuo Info.plist. Una chiave molto più importante che potresti aver già notato nello screenshot qui sopra è Enable Multiple Windows. Questa proprietà è impostata di default su NO. Impostare questa proprietà su YES permetterà agli utenti di aprire più finestre della vostra applicazione su iPadOS (o anche su macOS). Essere in grado di eseguire più finestre di un’applicazione iOS fianco a fianco è una differenza enorme rispetto all’ambiente a finestra singola con cui abbiamo lavorato fino ad ora, e la capacità di avere più finestre è l’intera ragione per cui il ciclo di vita della nostra applicazione è ora mantenuto in due posti piuttosto che uno.

Diamo un’occhiata più da vicino a AppDelegate e SceneDelegate per capire meglio come questi due delegati lavorano insieme per abilitare il supporto per le finestre multiple.

Comprendere i ruoli di AppDelegate e SceneDelegate

Se avete costruito applicazioni prima di iOS 13, probabilmente conoscete il vostro AppDelegate come l’unico posto che fa praticamente tutto ciò che riguarda il lancio della vostra applicazione, il foregrounding, il backgrounding e poi altro. In iOS 13, Apple ha spostato alcune delle responsabilità AppDelegate al SceneDelegate. Diamo un breve sguardo a ciascuno di questi due file.

Le responsabilità diAppDelegate

Il AppDelegate è ancora il principale punto di ingresso per un’applicazione in iOS 13. Apple chiama i metodi AppDelegate per diversi eventi del ciclo di vita dell’applicazione. Nel modello predefinito di Apple troverete tre metodi che Apple considera importanti da usare:

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

Questi metodi hanno alcuni commenti che descrivono effettivamente ciò che fanno in modo abbastanza dettagliato da capire cosa fanno. Ma esaminiamoli comunque velocemente.

Quando la vostra applicazione è appena lanciata, viene chiamato func application(_:didFinishLaunchingWithOptions:) -> Bool. Questo metodo è usato per eseguire la configurazione dell’applicazione. In iOS 12 o precedente, potreste aver usato questo metodo per creare e configurare un oggetto UIWindow e assegnato un’istanza UIViewController alla finestra per farla apparire.

Se la vostra applicazione sta usando delle scene, il vostro AppDelegate non è più responsabile di fare questo. Dal momento che la vostra applicazione può ora avere più finestre, o UISceneSessions attive, non ha molto senso gestire un oggetto finestra singola nel AppDelegate.

Il func application(_:configurationForConnecting:options:) -> UISceneConfiguration viene chiamato ogni volta che la vostra applicazione deve fornire una nuova scena, o finestra da visualizzare per iOS. Notate che questo metodo non viene chiamato quando la vostra applicazione viene lanciata inizialmente, viene chiamato solo per ottenere e creare nuove scene. Daremo uno sguardo più approfondito alla creazione e alla gestione di più scene in un post successivo del blog.

L’ultimo metodo nel modello AppDelegate è func application(_:didDiscardSceneSessions:). Questo metodo viene chiamato ogni volta che un utente scarta una scena, per esempio facendola scorrere via nella finestra del multitasking o se lo fai programmaticamente. Se la vostra app non è in esecuzione quando l’utente lo fa, questo metodo sarà chiamato per ogni scena scartata poco dopo che func application(_:didFinishLaunchingWithOptions:) -> Bool è stato chiamato.

In aggiunta a questi metodi predefiniti, il vostro AppDelegate può ancora essere usato per aprire URL, catturare avvisi di memoria, rilevare quando la vostra app terminerà, se l’orologio del dispositivo è cambiato significativamente, rilevare quando un utente si è registrato per le notifiche remote e altro.

Suggerimento:
È importante notare che se attualmente stai usando AppDelegate per gestire l’aspetto della barra di stato della tua app, potresti dover fare alcune modifiche in iOS 13. Diversi metodi relativi alla barra di stato sono stati deprecati in iOS 13.

Ora che abbiamo un quadro migliore di quali sono le nuove responsabilità del vostro AppDelegate, diamo un’occhiata al nuovo SceneDelegate.

Responsabilità dello SceneDelegate

Se considerate il AppDelegate come l’oggetto responsabile del ciclo di vita della vostra applicazione, il SceneDelegate è responsabile di ciò che viene mostrato sullo schermo; le scene o finestre. Prima di continuare, stabiliamo un po’ di vocabolario relativo alle scene, perché non tutti i termini significano ciò che potreste pensare che significhino.

Quando avete a che fare con le scene, ciò che sembra una finestra al vostro utente è in realtà chiamato un UIScene che è gestito da un UISceneSession. Quindi, quando ci riferiamo alle finestre, in realtà ci riferiamo agli oggetti UISceneSession. Cercherò di attenermi a questa terminologia il più possibile nel corso di questo post sul blog.

Ora che siamo sulla stessa pagina, guardiamo il file SceneDelegate.swift che Xcode ha creato quando ha creato il nostro progetto.

Ci sono diversi metodi nel file SceneDelegate.swift di default:

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

Questi metodi dovrebbero sembrarvi molto familiari se avete familiarità con i AppDelegate esistenti prima di iOS 13. Diamo prima un’occhiata a scene(_:willConnectTo:options:), questo metodo probabilmente sembra il meno familiare per voi ed è il primo metodo chiamato nel ciclo di vita di un UISceneSession.

L’implementazione predefinita di scene(_:willConnectTo:options:) crea la vostra vista iniziale del contenuto (ContentView se state usando SwiftUI), crea un nuovo UIWindow, imposta il rootViewController della finestra e rende questa finestra la finestra chiave. Potreste pensare a questa finestra come la finestra che l’utente vede. Questo, sfortunatamente, non è il caso. Le finestre esistono da prima di iOS 13 e rappresentano la viewport in cui opera la vostra app. Quindi, il UISceneSession controlla la finestra visibile che l’utente vede, il UIWindow che crei è la vista contenitore per la tua applicazione.

Oltre a impostare le viste iniziali, puoi usare scene(_:willConnectTo:options:) per ripristinare l’interfaccia utente della tua scena nel caso la tua scena si sia scollegata in passato. Per esempio, perché è stata mandata in background. Puoi anche leggere l’oggetto connectionOptions per vedere se la tua scena è stata creata a causa di una richiesta di HandOff o forse per aprire un URL. Vi mostrerò come farlo più avanti in questo post del blog.

Una volta che la vostra scena si è collegata, il prossimo metodo nel ciclo di vita della vostra scena è sceneWillEnterForeground(_:). Questo metodo viene chiamato quando la vostra scena prenderà la scena. Questo potrebbe essere quando la vostra applicazione passa dallo sfondo al primo piano, o se sta diventando attiva per la prima volta. Successivamente, viene chiamato sceneDidBecomeActive(_:). Questo è il punto in cui la vostra scena è impostata, visibile e pronta per essere utilizzata.

Quando la vostra app passa allo sfondo, vengono chiamati sceneWillResignActive(_:) e sceneDidEnterBackground(_:). Non mi addentrerò in questi metodi ora, poiché il loro scopo varia per ogni applicazione, e i commenti nel modello Xcode fanno un buon lavoro per spiegare quando questi metodi vengono chiamati. In realtà, sono sicuro che puoi capire da solo quando questi metodi vengono chiamati.

Un metodo più interessante è sceneDidDisconnect(_:). Ogni volta che la vostra scena viene mandata in background, iOS potrebbe decidere di disconnettersi e cancellare la vostra scena per liberare risorse. Questo non significa che la vostra app è stata uccisa o non è più in esecuzione, significa semplicemente che la scena passata a questo metodo non è più attiva e si disconnetterà dalla sua sessione.

Nota che anche la sessione stessa non è necessariamente scartata, iOS potrebbe decidere di ricollegare una scena a una sessione di scena in qualsiasi momento, per esempio quando un utente porta di nuovo una particolare scena in primo piano.

La cosa più importante da fare in sceneDidDisconnect(_:) è scartare qualsiasi risorsa che non è necessario tenere in giro. Questi potrebbero essere dati che sono facilmente caricati dal disco o dalla rete o altri dati che puoi ricreare facilmente. È anche importante assicurarsi di conservare tutti i dati che non possono essere facilmente ricreati, come per esempio qualsiasi input che l’utente ha fornito in una scena e che si aspetterebbe di trovare ancora lì quando ritorna in una scena.

Considera un’applicazione di elaborazione del testo che supporta scene multiple. Se un utente sta lavorando in una scena, poi la sfonda per fare qualche ricerca su Safari e cambiare la sua musica in Spotify, si aspetterebbe assolutamente che tutto il suo lavoro esista ancora nell’app di elaborazione del testo, anche se iOS potrebbe aver disconnesso la scena dell’app di elaborazione del testo per un po’. Per ottenere questo, l’app deve conservare i dati richiesti, e dovrebbe codificare lo stato attuale dell’app in un oggetto NSUserActivity che può essere letto più tardi in scene(_:willConnectTo:options:) quando la scena viene ricollegata.

Siccome questo flusso di lavoro di collegare, scollegare e ricollegare le scene separerà le buone app dalle grandi, diamo un’occhiata a come puoi implementare il ripristino dello stato nella tua app.

Eseguire una configurazione aggiuntiva della scena

Ci sono diverse ragioni per cui devi eseguire una configurazione aggiuntiva quando una scena viene configurata. Potresti dover aprire un URL, gestire una richiesta di Handoff o ripristinare lo stato. In questa sezione, mi concentrerò principalmente sul ripristino dello stato, poiché è probabilmente lo scenario più complesso che potresti dover gestire.

Il ripristino dello stato inizia quando la tua scena viene scollegata e viene chiamato sceneDidDisconnect(_:). A questo punto, è importante che la tua applicazione abbia già uno stato impostato che può essere ripristinato in seguito. Il modo migliore per farlo è usare NSUserActivity nella vostra applicazione. Se state usando NSUserActivity per supportare Handoff, Siri Shortcuts, indicizzazione Spotlight e altro, non avete molto lavoro extra da fare. Se non usate ancora NSUserActivity, non preoccupatevi. Una semplice attività utente potrebbe apparire un po’ come segue:

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

Nota che questa attività utente non è strutturata come Apple raccomanda, è un esempio molto scarno inteso a illustrare il ripristino dello stato. Per una guida completa su NSUserActivity, ti consiglio di dare un’occhiata alla documentazione di Apple su questo argomento.

Quando arriva il momento di fornire un’attività utente che può essere ripristinata in un momento successivo, il sistema chiama il metodo stateRestorationActivity(for:) sul tuo SceneDelegate. Nota che questo metodo non fa parte del template di default

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

Facendo questo associa l’attività utente attualmente attiva per una scena alla sessione della scena. Ricordate che ogni volta che una scena viene scollegata, il UISceneSession che possiede il UIScene non viene scartato per permettere alla sessione di riconnettersi ad una scena. Quando questo accade, scene(_:willConnectTo:options:) viene chiamato di nuovo. In questo metodo, avete accesso al UISceneSession che possiede il UIScene in modo da poter leggere il stateRestorationActivity della sessione e ripristinare lo stato dell’applicazione come necessario:

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}

Naturalmente, i dettagli di questo codice varieranno in base alla vostra applicazione, ma l’idea generale dovrebbe essere chiara.

Se ci si aspetta che il vostro UISceneSession gestisca un URL, potete ispezionare il urlContexts dell’oggetto connectionOptions per trovare gli URL che la vostra scena dovrebbe aprire e le informazioni su come la vostra applicazione dovrebbe farlo:

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

L’oggetto options conterrà informazioni sul fatto che la vostra scena dovrebbe aprire l’URL sul posto, quale applicazione ha richiesto l’apertura dell’URL e altri metadati sulla richiesta.

Le basi del ripristino dello stato in iOS 13 con il SceneDelegate sono sorprendentemente semplici, soprattutto perché è costruito su NSUserActivity, il che significa che molte applicazioni non dovranno fare troppo lavoro per iniziare a supportare il ripristino dello stato per le loro scene.

Tenete a mente che se volete avere il supporto per più scene per la vostra applicazione su iPadOS, il ripristino della scena è particolarmente importante in quanto iOS potrebbe disconnettere e riconnettere le scene quando passano dal primo piano allo sfondo e viceversa. Soprattutto se la vostra applicazione permette all’utente di creare o manipolare oggetti in una scena, un utente non si aspetterebbe che il suo lavoro sparisca se sposta una scena sullo sfondo per un momento.

In sintesi

In questo post del blog, avete imparato molto. Avete imparato quali ruoli i AppDelegate e i SceneDelegate svolgono in iOS 13 e come sono i loro cicli di vita. Ora sapete che il AppDelegate è responsabile della reazione agli eventi a livello di applicazione, come il lancio dell’app, per esempio. Il SceneDelegate è responsabile degli eventi relativi al ciclo di vita della scena. Per esempio, la creazione della scena, la distruzione e il ripristino dello stato di un UISceneSession. In altre parole, la ragione principale per cui Apple ha aggiunto UISceneDelegate a iOS 13 è stata quella di creare un buon punto di ingresso per le applicazioni multi-finestra.

Dopo aver imparato le basi di UISceneDelegate, avete visto un esempio molto semplice di come appare il ripristino dello stato in iOS 13 con UISceneSession e UIScene. Naturalmente, c’è molto di più da imparare su come la vostra app si comporta quando un utente genera più UISceneSession per la vostra app, e come queste scene potrebbero dover rimanere sincronizzate o condividere i dati.

Se volete saperne di più sul supporto di finestre multiple per la vostra app per iPad (o la vostra app per macOS), assicuratevi di controllare il mio post Aggiungere il supporto per finestre multiple alla vostra app iPadOS. Grazie per aver letto, e non esitare a contattarmi su Twitter se hai domande o feedback da darmi.

Mantieniti aggiornato con la mia newsletter settimanale

Practical Combine

Impara tutto quello che devi sapere su Combine e come puoi usarlo nei tuoi progetti con il mio nuovo libro Practical Combine. Avrai tredici capitoli, un Playground e una manciata di progetti di esempio per aiutarti a diventare operativo con Combine il prima possibile.

Il libro è disponibile come download digitale a soli $29.99!

Ottieni Practical Combine

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.