Applicazioni native Android e iOS – Parte 2

Come seconda parte di questa introduzione allo sviluppo mobile nativo, approcceremo il linguaggio Swift per vedere come realizzare un’applicazione nativa iOS tramite l’utilizzo dell’IDE proprietario di casa Apple Xcode. Vedremo le principali funzionalità del linguaggio supportato, la struttura di Xcode e proporremo un esempio di applicazione per iOS, come fatto per la controparte Google nell’articolo precedente.

Swift – Linguaggio snello ed evoluzione di Objective-C

Linguaggio di programmazione Object Oriented presentato da Apple nel 2014 ed atto allo sviluppo di applicativi per piattaforme macOS, iOS, watchOS, tvOS e Linux. È stato introdotto per superare la rigidità di Objective-C (linguaggio storico per lo sviluppo dei sistemi operativi Apple) e per permettere agli sviluppatori di avere feedback in tempo reale, ma allo stesso tempo integrarsi perfettamente con il codice Objective-C già presente. Apple sostiene che Swift, come intrinseco nel nome, sia un linguaggio molto veloce, tanto che lo mette a confronto con il suo vecchio Objective-C e Python eseguendo un algoritmo abbastanza complesso[1].

Swift non si distacca molto dalla tradizionale logica di sviluppo; dichiariamo variabili con var e costanti con let il cui tipo può essere implicitamente capito dal compilatore in base al valore assegnato, oppure esplicitamente dichiarato, similarmente a Kotlin.

var myVariable = 42
myVariable = 50
let myImplicitConstant = 42
let myExplicitConstant: Double = 42.5

Possiamo dichiarare array e dictionary nel modo consueto

var gameList = ["DarkSouls", "GodOfWar4", "ShadowOfWar"]
gameList[1] = "GodOfWar5"
var gameRating = [
    " DarkSouls": "Good",
    " ShadowOfWar": "Bad",
]
gameRating ["ShadowOfWar"] = "Worst"   

È interessante il fatto che su array sia possibile fare append di dati, in quanto questi crescono automaticamente ad ogni inserimento.

Per quanto riguarda il controllo del flusso i consueti if, switch e for sono all’ordine del giorno, ad eccezione del caso in cui vengano mescolati all’uso di let. Usare let nella condizione di un if ci permette di gestire guardie in caso di valori nullabili, ovvero se il let nella condizione risulta essere nil, la condizione ritorna false e non si entra nel corpo dell’if. Mentre se usato nello switch, ci permette di fare delle proiezioni sulla condizione del case per renderlo più rilassato a più casistiche.

In Swift si parla spesso di Closure, ovvero pezzi di codice riutilizzabili che hanno accesso a costanti e variabili presenti all’interno dello scope in cui le closure sono definite. Possiamo vederle come i nostri vecchi Delegate o alle odierne lambda. Una Func è una particolare Closure dichiarata con un nome e si avvicina molto alla concezione consueta dei metodi.

Function

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

Closure

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

Essendo Swift un linguaggio OOP, fa giustamente uso di classi, le quali sono dichiarate ed utilizzate come di consueto, al netto della definizione del costruttore tramite la keyword “init”. Per referenziare un oggetto all’interno dello scope della classe si fa uso della keyword “self”. Infine, oltre all’uso consueto dei generics a tutti i livelli di codice (classi, func e closure),

le classi in Swift possono estendere delle super classi, come in C# o Java, o implementare un protocol (interfaccia).

Struttura del progetto in Xcode

Aprendo Xcode ci viene proposto di aprire o creare un progetto Xcode, oppure di iniziare con un playground. Quest’ultimo è un ambiente di sviluppo interattivo che permette agli sviluppatori di mettere mano al codice, interagendoci direttamente e permettendo di vederne i risultati in tempo reale. Per esempio, il playground offerto da Apple per approcciarsi a Swift.

Un Playground, come possiamo vedere dall’immagine precedente, è caratterizzato da tre porzioni: la prima a sinistra mostra messaggi a livello di build e Runtime. Quella centrale mostra del codice tipicamente commentato, con la possibilità di eseguirne delle porzioni. Mentre la parte a destra mostra il risultato dell’esecuzione del codice scelto.

Creando invece un nuovo progetto, Xcode ci propone la struttura da cui partire.

Possiamo vedere come l’IDE proponga, oltre al tipo di applicazione, anche la piattaforma prescelta. Il nostro obiettivo è fare un’applicazione iOS con navigazione a tab; nonostante ci sia il template, in questo caso partiamo da una situazione pulita (Single View App) ed aggiungiamo man mano le classi ed i file che ci servono. Premendo su next, Xcode ci chiede ulteriori informazioni

Le informazioni richieste sono simili a quelle contenute nel file manifest in un’applicazione Android, come Bundle Identifier ed il Product Name. Due opzioni degne di nota:

  • Team: nel mondo Apple di base non ci si può muovere senza avere un Apple ID. Tuttavia, se si vuol sviluppare un’applicazione iOS, sarebbe meglio possedere anche un Account Developer ed un Provisioning Profile; l’unione di queste due entità ci permette di testare la nostra applicazione in debug su dispositivi fisici (altrimenti possiamo testarla solo su simulatore iOS, sempre previo possesso di una building machine Apple), e di rilasciarne delle release. Le informazioni del Team vengono prese dall’Apple ID, il quale, se collegato ad un account developer, fornirà anche le ulteriori informazioni necessarie.
  • User Interface: In Xcode ci sono due modi di definire le viste delle nostre pagine: tramite codice in sintassi fluent (SwiftUI) e tramite editor e template (Storyboard). SwiftUI è stato introdotto da relativamente poco tempo e permette appunto di definire le viste tramite sintassi fluent, visualizzando il risultato in una specie di HotRealod. Noi ci baseremo sulle Storyboard, poiché sono un meccanismo completo e stabile che ci permette di definire le viste tramite editor grafico.

Una volta creato il progetto, vediamo come Xcode organizza le varie informazioni nella figura precedente. Sulla sinistra troviamo la struttura vera a propria del progetto, in cui vediamo i file .swift, di cui l’AppDelegate è l’entry point della nostra applicazione (il quale ne gestisce il ciclo di vita), il ViewController è la classe che funge da controller per la vista di default definita nello storyboard, mentre lo SceneDelegate è un nuovo meccanismo introdotto a partire da iOS 13, che si occupa di cosa è mostrato sullo schermo del dispositivo. In particolare, ragiona di UIScene e UISceneSession, ovvero schermate e gestori di schermate.

In mezzo alla figura, vediamo una schermata di impostazioni se ci troviamo in un file che le contiene, come il file di progetto o il file info.plist (che gestisce informazioni per l’app come il bundle, permessi etc.). Se invece ci troviamo in un file storyboard, vedremo la preview della nostra vista, mentre se ci troviamo in un file .swift ne vedremo il contenuto a schermo. A sinistra troviamo un pannello di proprietà, tipicamente interessante quando stiamo visualizzando un file storyboard.

Entrando nel dettaglio, i file storyboard sono due:

  • LaunchScreen.storyboard: Definisce lo splash screen della nostra applicazione, andando sostituire il vecchio Xib.
  • Main.Storyboard: Definisce le schermate e le “partial” dell’intera applicazione, collegandole fra di loro tramite meccanismi di navigazione.

Per fare un esempio aggiungiamo una label su entrambi i file, ne valorizziamo i testi e lanciamo l’applicazione. Di seguito possiamo vedere lo splash screen nella prima immagine e la schermata principale nella seconda.

Navigazione a tab

Per poter indicare al nostro progetto di gestire una navigazione a tab, ci sono più possibilità. Noi lavoreremo a livello di storyboard (alternativamente, avremmo potuto istanziare le classi che ci servono direttamente nello AppDelegate), quindi rimuoviamo le viste ed i controller che avevamo definito per l’hello world,
apriamo il main storyboard e visualizziamo l’object library(Shift+CMD+L). Selezioniamo Tab Bar Controller e lo trasciniamo all’interno dello storyboard.

Notiamo che di default il sistema configura due tab con i relativi controller. Xcode ha infatti inserito un controller con una TabBarView dedicata, al cui interno sono presenti due tab. Le frecce in grigio (che collegano la schermata con due tab alle altre due) sono dei constraint di navigazione, che definiscono il flusso dell’applicazione quando si fa tap sui tab.

Se non già impostato, scegliamo il tab bar controller come controller iniziale dell’applicazione. Per fare questo, selezioniamo il controller, andiamo nel menu Attribute Inspector e spuntiamo “Is Initial View Controller”. In questo modo, allo startup dell’applicazione verrà caricato il controller selezionato e seguirà il suo flusso di navigazione.

Dato che vogliamo customizzare le tab della nostra applicazione, creiamo due view controller dedicati, facendo drag and drop dei controller che vogliamo aggiungere sul workspace; in particolare, aggiungiamo un TableViewController ed un ViewController. Successivamente, dobbiamo definire un Segue. I Segue sono i constraint di navigazione accennati prima. Quindi trasciniamo il mouse premendo click sinistro + ctrl dal tab controller al controller per cui vogliamo gestire il primo tab e nella finestra che appare scegliamo “Relationship Segue -> view controllers”. Facciamo la stessa cosa per l’altro controller in modo da definire i flussi dei due tab. Per personalizzare i nomi dei tab, andiamo sul Tab Bar Item del controller di dettaglio e nell’Attribute Inspector possiamo cambiare il Title a piacere.

Sul workspace e facendo partire l’app, avremo le seguenti situazioni:

Liste e chiamate HTTP

Come di consueto, vediamo come fare a definire una lista in Swift ed a popolarla con degli elementi. Abbiamo già inserito come controller del primo tab un TableViewController, questo ci permette di aver già pronta una struttura che gestisce nativamente collezioni di dati, sfruttando tutto il meccanismo di recycle del rendering degli item sullo schermo del device, quindi ottimizzando l’applicazione anche per grandi quantità di dati.
Partiamo definendo la struttura dati che modella le informazioni degli items della nostra lista, dunque creiamo una nuova classe Cocoa Touch Class e le facciamo estendere la classe base.

UITableViewCell, in modo da poter customizzare l’aspetto dell’item. Apriamo la libreria e trasciniamo una TableViewCell all’interno del nostro TableViewController, poi clicchiamo sulla TableViewCell ed all’interno dell’Attribute Inspector incolliamo nel campo Identifier il nome della classe custom che abbiamo creato. In questo modo Xcode assocerà come items della TableView le nostre TableViewCell custom. A questo punto personalizziamo la ViewCell come meglio ci piace; nel nostro caso, aggiungiamo due label trascinandole dalla Library direttamente sopra la cella. Gli elementi della TableViewCell appena inseriti vanno collegati alla classe che la definisce. Per Fare questo trasciniamo gli elementi da storyboard alla classe premendo CTRL e mettendo l’IDE in una situazione a doppia schermata, in modo da vedere da una parte il codice e dall’altra la classe. Facendo così Xcode inserirà le corrette proprietà all’interno della classe.

Dopo aver definito i controlli occorre aggiungere dei constraint, in modo tale che le label siano vincolate al loro contenitore (la cella stessa). Per fare ciò, facciamo drag and drop -tenendo premuto CTRL- dei controlli che vogliamo vincolare e scegliamo il tipo di vincolo da quelli proposti nella popup che appare. Come possiamo vedere dalle immagini successive, possiamo aggiungere vincoli come top, bottom, leading o trailing space fra controlli.

Questo ci permetterà di avere una vista formattata come ci aspettiamo. È possibile visualizzare i constraint aggiunti cliccando sui controlli ed Xcode ce li mostrerà evidenziati in colori diversi.

Come possiamo vedere i constraint in rosso sono considerati da Xcode come superflui o addirittura nocivi per il corretto rendering. Fortunatamente, è abbastanza intelligente da proporre un set automatico di constraint che ritiene sufficiente allo scopo. Per fare ciò ci basta cliccare nella penultima icona di constraint in basso a destra nello storyboard e scegliere l’opzione “Reset to Suggested Constraints”. Il risultato è il seguente.

Arrivati a questo punto dobbiamo iniziare a caricare i dati nella nostra TableView. Per prima cosa creiamo una Classe custom che eredita da UITableViewController e la associamo al tab con la TableView; in questo modo possiamo customizzare il comportamento della schermata. Successivamente creiamo una classe Swift in cui definiamo la struttura dati da cui ricevere le informazioni per gli elementi della tabella e ne istanziamo un array nel nostro UITableViewController

Il TableViewController per funzionare ha bisogno di definire tre metodi in override. I primi due, come nella figura successiva, definiscono rispettivamente il numero di sezioni della TableView ed il numero di elementi da renderizzare.

Il terzo, come in figura successiva, definisce le politiche di riuso degli elementi della TableView, ovvero, invece di creare e rimuovere elementi quando si fa scroll, questi vengono messi in una coda di riuso (Reuse Queue), per poi essere presi al bisogno. Vediamo dunque come ordinare alla cella di renderizzare le informazioni che vogliamo.

Nella figura precedente, possiamo vedere come riusciamo ad istanziare la nostra cella custom tramite apposito Identifier. L’istruzione “guard” permette di eseguire codice solo se una condizione è verificata. Nel nostro caso la condizione è il cast safe (as?) a CharacterTableViewCell; se il cast non viene eseguito, lanciamo un’eccezione. Successivamente, una volta ottenuta l’istanza della cella e l’istanza dell’elemento della lista di dati, valorizziamo i testi delle label della cella con le info del personaggio. Il risultato è il seguente

Come passo successivo proviamo a collegare la lista sopra definita a dei dati ricevuti dal web tramite chiamata http.

Per fare ciò ci appoggiamo alla classe URLSession, che ci espone il metodo dataTask con il quale effettuare la chiamata GET. Come possiamo vedere dall’immagine successiva, il risultato del metodo ritorna il content della risposta, la risposta e l’eventuale errore. Noi non facciamo altro che appoggiare il contenuto della risposta in una variabile per poi stamparlo, se questo è coerente, altrimenti stampiamo l’errore.

Ora dobbiamo deserializzare il contenuto della risposta sulla nostra classe, in modo da popolare la collezione di dati da far vedere nella TableView.

Per fare questo, serializziamo i dati ricevuti dal web in formato json tramite la classe JSONSerialization. Castiamo a String l’oggetto json ed estraiamo il dato che ci serve. In questo caso il dato è composto da una collezione di dictionary, dunque ciclando dentro un for prendiamo le proprietà necessarie, istanziamo l’oggetto RMCharacter valorizzandolo con le proprietà appena estratte e aggiungiamo l’oggetto nella nostra collezione.

Al termine del caricamento dei dati in memoria, forziamo il reload della TableView in modo da sincronizzare la vista. In alternativa avremmo potuto utilizzare un lock per sincronizzare la chiamata.

Eseguiamo il codice con il seguente risultato:

Pubblicazione

Come sempre, per poter pubblicare un’applicazione occorre prima prepararla al submit. Ovvero dobbiamo accertarci che il progetto sia compilante, definire tutte le informazioni necessarie nell’info.plist, come Bundle Identifier, Version e Build, accertarci di essere “in regola” con l’Apple ID ed avere disponibile un account Developer ed un Provisioning Profile. È facile vedere se sia configurato un Apple ID andando su Preferences -> Accounts di Xcode.

Una volta configurato tutto, basterà scegliere come build target “Generic iOS Device”

Andare su Product di Xcode e cliccare su Archive. Xcode inizierà a generare il bundle dell’applicazione.

Una volta generato il bundle, lo selezioniamo e clicchiamo su Distribuite App.

Scegliamo il metodo di distribuzione, tipicamente Ad Hoc se vogliamo poi successivamente caricare manualmente il file dell’applicazione su Store o su device, o iOS App Store se vogliamo direttamente mandarlo in revisione allo Store.

Una volta caricato, potremo andare su App Store Connect e verificare lo stato della build della nuova versione e scegliere se caricarla in test su TestFlight o fare una nuova Review da mandare sullo Store.

Conclusioni

Anche in questo caso, la maggior difficoltà è stata entrare nel mondo nuovo di Swift e di Xcode, sebbene quest’ultimo, al netto degli shortcut bizzarri, mi ha stupito per la sua facilità d’uso. Swift è un linguaggio agile e dal punto di vista sintattico, mi ha dato l’impressione di essere un po’ più guidato rispetto all’andare allo sbaraglio con Kotlin. A differenza di Android, lo startup di un’app in Swift è pressoché identico alla versione Xamarin. Infine, essendo cresciuto nel mondo Xamarin, continuo a non trovare una stringente necessità di passare al nativo, se non esplicitamente richiesto dal cliente.


[1] https://www.apple.com/it/swift/

Se ti sei perso la prima parte di questo articolo clicca QUI!