L'oggetto View in SwiftUI

SwiftUI è un framework di natura dichiarativa. Il risultato visuale di quello che vediamo nel nostro dispositivo è frutto di una serie di dichiarazioni che facciamo al nostro compilatore su come vorremmo vedere la nostra interfaccia.  Ma se in UIKit il cuore pulsante erano le UIView, quale è il paritetico in SwiftUI? La risposta è un nuovo oggetto, denominato View

Cosa è una View?

La prima reale e grossa differenza rispetto ad una UIView è il fatto che in SwiftUI una View è una struct conforme ad un protocollo. L'esempio base fornito da Apple, esordisce così:


struct ContentView: View {
   var body: some View {
      Text("Hello World")
   }
}

Quello sopra è un oggetto SwiftUI perfettamente funzionante dal nome ContentView e che contiene (restituisce) nel suo body una label con scritto "Hello World".

Noterete che ho parlato del concetto di "contenere" (restituire è stato solo accennato tra parentesi). Uno dei capisaldi di SwiftUI, infatti, è la riusabilità del codice ed il concetto di View riutilizzabili come se fossero dei mattoncini Lego™.

figure

Fig.1 - La gerarchia ad albero, del componente ContentView dell'esempio Apple.

Una view, dunque, definisce un pezzo della nostra Interfaccia Utente.

Ogni cosa all'interno di un'app in SwiftUI è riconducibile ad una View. Quest'ultima definisce, dunque, un pezzo della nostra interfaccia. Come conseguenza, le viste complesse sono costruite da pezzettini elementari che cooperano e lavorano insieme creando una gerarchia ad albero. Dietro le quinte, il compilatore gestisce tutte le View in una struttura dati altamente efficiente che rende a schermo i contenuti richiesti. Ma come funziona questa composizione?

Un unico requisito: body

Abbiamo detto sopra che una View è una struct conforme al protocollo View. L'unico requisito di questo protocollo è fornire alla nostra View un body responsabile del contenuto del nostro oggetto. Il body è una speciale closure che restituisce un contenuto anch'esso sotto forma di View. Ad esempio, nel codice sopra fornito da Apple:


...
   var body: some View {
      Text("Hello World")
   }
...

Il body della nostra View è un componente base, denominato Text che restituisce a sua volta un oggetto conforme alla richiesta di tipo some View espressa nel body. Questo sintassi fa parte di un nuovo concetto esposto da Swift 5.1 denominato Opaque Types. Per ora ci basta sapere che il tipo opaco richiesto dal body, ci permette di intercambiare con efficacia oggetti dei quali non si conosce a priori il tipo specifico ma che è ben determinato a livello della compilazione (a differenza di un semplice protocollo). 

I tipi opachi

Ritornare un tipo opaco sembra molto simile ad usare un protocollo. Tuttavia, questi due costrutti differiscono nel fatto che il tipo opaco preserva il tipo. Un tipo opaco si riferisce ad un tipo specifico, anche se il chiamante della funzione non è in grado di vedere di quale tipo si tratta. Un protocollo, invece, può riferirsi a qualunque tipo conforme ad esso. Questi ultimi offrono più flessibilità riguardo al tipo restituito. I tipi opachi, invece, ci permettono di avere garanzie specifiche in merito al tipo restituito.

Proviamo a fare un esempio, per chiarire meglio il concetto:


import UIKit

protocol Umanoide {
}

struct Donna : Umanoide {
}

struct Uomo : Umanoide {
}

func bambino() -> Umanoide {
	Bool.random() ? Donna() : Uomo()
}

Il protocollo Umanoide è adottato da due strutture: Uomo e Donna. La funzione bambino, restituisce un tipo Umanoide che può essere in maniera equamente probabile uno dei due casi citati. A questo punto, creiamo due istanze di umanoide:


let primogenito = bambino()
let secondogenito = bambino()

e visto che siamo curiosi di sapere se i nostri figli sono maschi o femmine, proviamo a confrontarli:


print((primogenito == secondogenito))

Il compilatore, a questo punto si arrabbierà, dicendoci - giustamente - che la classe umanoide non è comparabile perchè non adotta il protocollo Equatable. Poco male, penserete. Proviamo ad adottarlo:

figure

Fig.2 - L'output del playground.

Il problema è che Umanoide non può essere confrontato. Perchè Swift non ha nessuna garanzia che gli oggetti restituiti siano di tipo confrontabile. Ricordiamoci che qualunque struttura può adottare il protocollo, implementandone i metodi richiesti. 

Per poter risolvere questo problema, nascono i tipi opachi. Il codice sopra, può essere trasformato nel seguente modo:


import UIKit

protocol Umanoide : Equatable {
}

struct Donna : Umanoide {
}

struct Uomo : Umanoide {
}

func bambino() -> some Umanoide {
	Uomo()
}

func bambina() -> some Umanoide {
	Donna()
}

let primogenito = bambino()
let secondogenito = bambina()

Un importante osservazione da fare al codice sopra è che, come noterete, ho dovuto creare due funzioni diverse bambino() e bambina() perchè uno dei requisiti principali quando lavoriamo con un tipo opaco è che i metodi devono restituire solo un tipo di oggetto. Ritroverete spessissimo in SwiftUI questo concetto quando lavoreremo con le View.

Per i più curiosi - I tipi opachi in Swift 5.1

I modifiers e le View

Abbiamo visto come creare un oggetto View e come soddisfare i requisiti minimi del protocollo View per i nostri oggetti custom. Ma per personalizzare un oggetto, in realtà, è necessario agire su proprietà basilari: carattere, colore, ombre, impaginazione.

SwiftUI ci mette a disposizione un concetto semplice e potente per soddisfare questo requisito, denominato modificatori o modifiers.

figure

Un modifier è semplicemente un metodo che crea una nuova view da una view esistente. Una rappresentazione ad albero potrebbe essere la seguente:

figure

Fig.2 - Semplice gerarchia che illustra l'uso del modificatore .font

Inoltre, questo comportamento può essere reiterato più volte:

figure

Fig.3 - I modificatori possono essere annidati come semplici View.

In pratica, i modifiers sono anch'essi dei costruttori di View che modificano il comportamento dell'elemento precedente attraverso un meccanismo denominato a catena o chained.

A prima vista, se siamo abituati a lavorare con UIKit, questo meccanismo può sembrare poco efficiente e poco ortodosso: stiamo crenado View multiple che fanno la stessa cosa, cambiando solo alcune proprietà della stessa. Ma dobbiamo ricordare che in SwiftUI tutto è costituito da struct.

Queste strutture sono altamente efficienti in fatto di creazione ed allocazione, perchè lavorano direttamente sullo stack di memoria e non sullo heap come le corrispondenti class. Inoltre, non dobbiamo dimenticare che ci troviamo di fronte ad un nuovo paradigma di tipo dichiarativo e questo comportamento è perfettamente normale - anzi, è quello consigliato dalla stessa Apple - per lavorare con le view.

Il compilatore, dietro le quinte, saprà attimizzare le nostre richieste (colore, tipo di carattere, etc.) in una struttura dati efficiente senza doverci preoccupare delle performance.

Per i più curiosi - Classi e Struct in SwiftUI

In questo primo articolo, abbiamo visto i concetti basi che si celano dietro SwiftUI. Questo nuovo framework è ancora in una prima fase di beta e sicuramente subirà cambiamenti importanti prima del rilascio finale con iOS13. Il mio consiglio è quello di continuare a seguirlo, studiarlo e sperimentare. Come ogni cosa nuova, deve essere esplorata e provata in pratica, prima di saltare a qualunque tipo di conclusione. 

Vi auguro buon lavoro!

Prossimo articolo: SwiftUI - Costruzione di View complesse

© Copyright 2019 Sofapps - All Rights Reserved - VAT 05197020877