Programmare in SwiftUI: l'oggetto View

Cos'è una View in SwiftUI: una struct che adotta un protocol con un solo requisito, il body, e come gli opaque type si differenziano dai protocol.

Read in English

Tradotto dall’inglese con l’aiuto dell’IA e rivisto dall’autore.Leggi l’originale

Una persona in piedi nell'acqua bassa, lontana dalla riva, sotto un cielo pallido, con qualche scoglio all'orizzonte

Abbiamo visto che SwiftUI è un framework dichiarativo. Il risultato visivo che vediamo sul nostro dispositivo nasce da una serie di istruzioni con cui diciamo al compilatore come vogliamo vedere la nostra interfaccia. Ma se la UIView era il cuore pulsante di UIKit, qual è l'equivalente in SwiftUI? La risposta è un nuovo oggetto, chiamato View.

Che cos'è una View?

La prima vera grande differenza rispetto a una UIView è che in SwiftUI una View è una struct che adotta un protocol. L'esempio base fornito da Apple comincia così:

Swift

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

Quello qui sopra è un oggetto SwiftUI perfettamente funzionante, chiamato ContentView, che contiene (restituisce) nel suo body una label con "Hello World".

Noterete che ho parlato di "contenere" (return l'ho solo accennato tra parentesi). Uno dei cardini di SwiftUI è infatti la riusabilità del codice e il concetto di View riutilizzabili come se fossero mattoncini Lego ™.

Gerarchia ad albero dell'esempio precedente.
Fig.1: Gerarchia ad albero dell'esempio precedente.

Una view, quindi, definisce un pezzo della nostra User Interface.


Tutto in un'app SwiftUI può essere ricondotto a una View, che definisce un pezzo della nostra interfaccia. Di conseguenza, le view complesse sono costruite a partire da elementi semplici che cooperano tra loro, formando una gerarchia ad albero. Dietro le quinte, il compilatore gestisce tutte le View e le traduce in una struttura dati molto efficiente che mostra a schermo il contenuto richiesto. Ma come funziona questa composizione?

Un solo requisito: body

Abbiamo detto che una View è una struct che adotta il protocol View. L'unico requisito di questo protocol è fornire alla nostra View un body, responsabile del contenuto del nostro oggetto. Il body è una closure speciale che restituisce un contenuto anch'esso sotto forma di View. Per esempio, nel codice di Apple visto sopra:

Swift

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

In questo caso il body della nostra View è un componente di base, chiamato Text, che a sua volta restituisce un oggetto conforme al tipo some View richiesto dal body. Questa sintassi fa parte di un concetto nuovo introdotto con Swift 5.1, chiamato Opaque Types. Per ora basta sapere che l'opaque type richiesto dal body ci permette di scambiare oggetti di cui non conosciamo in anticipo il tipo specifico, ma che è ben determinato a livello di compilazione (a differenza di un semplice protocol).

Opaque types

Restituire un opaque type sembra molto simile a usare un protocol, ma i due costrutti differiscono perché l'opaque type conserva il tipo. Un opaque type si riferisce a un tipo specifico, anche se chi chiama la funzione non è in grado di vedere quale sia. Un protocol, invece, può riferirsi a qualunque tipo che lo adotti: offre quindi più flessibilità sul tipo restituito, mentre gli opaque type ci danno garanzie precise sul tipo restituito.

Facciamo un esempio per chiarire meglio il concetto:

Swift
mport UIKit

protocol Humanoid {
}

struct Woman : Humanoid {
}

struct Man : Humanoid {
}

func child() -> Humanoid {
	Bool.random() ? Woman() : Man()
}

Il protocol Humanoid è adottato da due struct: Man e Woman. La funzione child restituisce un Humanoid che può essere, con uguale probabilità, uno dei due casi. A questo punto creiamo due istanze di Humanoid:

Swift

let first = child()
let second = child()

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

Swift

print((first == second))

A questo punto il compilatore ci rimprovererà, giustamente, perché Humanoid non è confrontabile: non adotta il protocol Equatable. Niente male, penserete. Proviamo ad adottarlo:

Output del Playground.
Fig.2: Output del Playground.

Il problema è che Humanoid non può essere confrontato perché Swift non ha alcuna garanzia che gli elementi restituiti siano di un tipo confrontabile. Ricordo che qualunque struct può adottare il protocol, implementando i metodi richiesti.

Per risolvere questo problema sono nati gli opaque type. Il codice precedente può essere trasformato così:

Swift

import UIKit

protocol Humanoid : Equatable {
}

struct Woman : Humanoid {
}

struct Man : Humanoid {
}

func boy() -> some Humanoid {
	Man()
}

func girl() -> some Humanoid {
	Woman()
}

let primogenito = boy()
let secondogenito = girl()

Un'osservazione importante sul codice qui sopra: come potete vedere, ho dovuto creare due funzioni diverse (boy per il maschio, girl per la femmina), perché uno dei requisiti principali quando si lavora con un opaque type è che i metodi devono restituire un solo tipo di oggetto. Ritroverete questo concetto molto spesso in SwiftUI quando lavoreremo con le View.

Continua a leggere

Tutti gli articoli
  1. Programmare in SwiftUI: ObjectBinding e BindableObject
  2. Programmare in SwiftUI: introduzione agli stati
  3. Programmare in SwiftUI: costruire view più complesse
↑ ↓ naviga↵ apriesc chiudi