I modifiers in SwiftUI offrono un modo semplice ed intuitivo per arricchire le tue View con stili ed attributi che ti aiuteranno a creare UI accattivanti.

Un modifier, come la parola suggerisce, è un modificatore di View. Dietro le quinte prende la View, gli applica delle modifiche e la restituisce al Canvas già modificata. Tu dovrai solamente preoccuparti di utilizzare il modifier corretto, al resto ci pensa SwiftUI.

Infatti, grazie ai Modifier, potrai cambiare il Font dei tuoi Text, arrotondare View, aggiungere un Background o cambiare il colore delle tue view (ed ovviamente la lista non si ferma qui).

Alla fine di questa lezione sarai in grado di padroneggiare un primo set di modifiers che ti permetteranno di creare layout del genere:

Excited 😍?
Allora crea un nuovo progetto iOS in SwiftUI e diamoci dentro!

Struttura di una Modifier

Un Modifier in SwiftUI è una funzione che prende in input la View, gli applica delle modifiche e la restituisce in output modificata:

Immagina una funzione in Swift come un forno in cucina. Tu sai che mettendogli qualcosa dentro, e dopo averlo acceso e settato la temperatura (molto importante 😂), ti restuisce una pietanza riscaldata o cucinata.

Se potessimo trasformare il forno in una funzione in programmazione potremmo dire che ha in input la pietanza da cucinare e la temperatura ed in output il piatto cucinato.

Nel linguaggio Swift le funzioni si scrivono utilizzando la parola func (un po' come var abbiamo scoperta crea variabili) e conclude con una lista di input tra parentesi tonde ed una freccia con la tipologia di output che restituirà:

func bake(temperature: Number, plate: Plate) -> Plate {
   // Do something with the plate
   return plate
}

So che sono andato un po' veloce. Le funzioni in Swift saranno un argomento che tratteremo nel dettaglio nelle prossime lezioni. Per il momento mi interessava solamente accennarti l'argomento per farti capire il dietro le quinte di un modifier

Font e Weight

Partiamo dalla barra superiore che chiameremo bannerView, crea una nuova var di tipo some View ed al suo interno aggiungi una HStack con due Text separati da uno Spacer. Poi passa la bannerView dentro il body:

var bannerView: some View {
    HStack {
        Text("Modifiers")
        Spacer()
        Text("more")
    }
}

🤔 Peppe, che lingua stai parlando? Se HStack, var e Spacer ti suonano nuovi, molto probabilmente non hai seguito la lezione precedente. Se sei qui per la prima volta dai anche un'occhiata al corso SwiftUI Tour. 

Adesso diamo un po' di carattere a questi Text assegnandogli un font.

Come?

Una View fornisce un set di funzionalità o funzioni che possiamo utilizzare per arricchirla. Puoi accedere a queste funzioni scrivendo il simbolo punto . dopo il nome di una View o variabile:

Tutte quelle voci con il simbolo M sono i modifiers che potrai utilizzare (anche se quella M sta ad indicare Methods che è l'equivalente di funzione).

Se scrivi .font subito dopo il punto vedrai comparire un menu del genere e la prima voce è il modifier che utilizzeremo:

Nella parte inferiore del pannello trovi quello che comunemente viene chiamato signature del metodo/funzione. Nel caso di font è:

func font(_ font: Font?) -> Text 

Nel paragrafo precedente ti ho parlato della sintassi di una modifier/funzione ed in questo caso capiamo che:

  • func font ci fa capire che è una funzione ed il suo nome è font

    • Quando la utilizzi non c'è bisogno di scrivere func

  • (_ font: Font?) dentro le parenti tonde c'è la lista degli argomenti. In questo caso uno solo che si chiama font e vuole un oggetto di tipo Font.

    • Il punto interrogativo dopo Font? sta ad indicare che l'input è Optional o opzionale. Capire meglio quali sono le implicazioni più in là.

    • Font, un po' come Text, View, HStack etc è una parola di SwiftUI e come puoi immaginare fornisce accesso ai font.

  • Infine la freccia -> Text ci fa capire che il metodo restituirà il Text, su cui lo stiamo utilizzando, con il font applicato. 

    • Come per func la freccia non va scritta quando si utilizza, ci fa solo capire cosa farà il metodo.

Proviamo ad usarlo?

SwiftUI fornisce una lista di font di default e questi sono accessibili scrivendo Font.nomeFont:

Font.nomeFont
// esempio
Font.title
Font.subtitle 
// etc

Quindi all'interno del modifier scriverai:

Text("Modifiers")
    .font(Font.title)
// oppure puoi omettere Font e scrivere
Text("Modifiers")
    .font(.title)

Se applichi il font title questo è quello che dovrebbe apparire:

👋 Prima di andare avanti, prova ad utilizzare altri tipi di font come per esempio headline, caption o body

Per concludere questa sezione, il modifier fontWeight permette di aggiungere o rimuovere variazioni di grassetto. Quindi usalo sul Text subito dopo il font selezionando il weight di tipo bold:

Text("Modifiers")
    .font(.title)
    .fontWeight(.bold)

Il tipo di dato del fontWeight modifier fa parte del tipo Font. Infatti, la sua sintassi è Font.Weight ovvero Weight ha senso solamente nel contesto del Font. Quindi puoi scrivere il valore in queste due sintassi:

fontWeight(Font.Weight.light)
fontWeight(.medium)

Generalmente si utilizza la contratta, ovvero punto ed opzione che vuoi utilizzare.

Accenno di Opzionalità

Quando abbiamo provato ad usare il modifier fontWeight o lo stesso font abbiamo notato che il type aveva uno strano punto interrogativo alla fine:

func font(_ font: Font?) -> View
func fontWeight(_ weight: Font.Weight?) -> View

Il punto interrogativo, dopo il nome di un type, esempio: CGFloat?, View?, Font? etc sta ad indicare che il valore può essere opzionale.

Optional per il linguaggio Swift è un tipo di dato particolare che può avere due valori:

  1. nil 

  2. o un valore appartenete al type.

Per SwiftUI, quando passeremo nil ad un modifier stiamo ad indicare che vogliamo utilizzare il valore di default del modificatore.

Text("hello")
   .font(nil)
   .fontWeight(nil)

In pratica è un po' come annullare l'effetto del modificatore. In genere si utilizza un valore Optional in situazioni dove, per esempio:

  • Al cliccare di un bottone vogliamo annullare una serie di effetti ad una View.

  • Dobbiamo eseguire il download di una lista di ricette e ci vuole del tempo per farlo, quindi la variabile partirà con il valore nil` e poi gli assegneremo il valore non appena la lista è stata scaricata.

  • Vogliamo scaricare un'immagine e conservarla in una var. Questa parte con nil e quando il download finisce passeremo l'immagine in questione.

Scenderemo nei dettagli dell'opzionalità nel prossimi moduli, per il momento era importante accennarti cosa fa dato che incontrerai questo punto interrogativo in moltissimi modifier.

Background

Una volta capito come utilizzare un modifier gli altri vengono quasi di conseguenza. Uno dei pochi che però richiede un po' di attenzione in più è il modifier background.

Il modifier background permette di aggiungere uno stile o una nuova view come sfondo della view su cui viene utilizzato.

Se infatti provi ad utilizzarlo sulla bannerView vedrai che la lista dei metodi ti suggerisce diverse opzioni:

Al momento quello su cui voglio focalizzarmi è il modifier che ci permetterà di settare uno style o una Shape al nostro background:

func background(_ style: ShapeStyle, in shape: Shape) -> View
/// oppure solo style
func background(_ style: ShapeStyle) -> View
// o solo shape
func background(in shape: Shape) -> View

Uno dei primi style che imparerai ad utilizzare è il Color.

In SwiftUI i colori vengono categorizzati come view e style. Di conseguenza, potrai utilizzare un colore come sfondo di un'altra view.

SwiftUI fornisce dei colori di default che sono accessibili utilizzando la sintassi Color.nomeColore:

Color.red
Colore.orange
Color.gray
Color.blue
....

Di conseguenza, proviamo ad applicare il Color.orange come background della bannerView:

var bannerView: some View {
    HStack {
        Text("Modifiers")
            .font(.title)
            .fontWeight(.bold)
        Spacer()
        Text("more")
    }
    .background(Color.orange)
}

💡 Aggiungi il modifier padding prima del background per creare un po' di spazio tra il contenuto della HStack ed il background.

Ora, immaginiamo di voler arrontondare i bordi di questo header. Ci sono due modi per farlo:

  1. Aggiungere un altro modifier, subito dopo il background, chiamato cornerRadius.

  2. Oppure utilizzare la Shape RoundedRectangle come valore del modifier background.

Il modifier cornerRadius vuole come primo parametro un valore di radius di tipo CGFloat ovvero un numero con o senza virgola:

func cornerRadius(_ radius: CGFloat) -> View

Oppure, puoi ottenere lo stesso risultato in una singola istruzione, utilizzando la shape RoundedRectangle:

RoundedRectangle(cornerRadius: CGFloat)

direttamente sul modifier background:

.background(
    RoundedRectangle(cornerRadius: 8)
)

Di default vedrai che la shape è di colore nero. Per cambiarlo, aggiungi il modifier fill(Color.nomeColore) alla RoundedRectangle:

.background(
    RoundedRectangle(cornerRadius: 8)
        .fill(Color.orange)
)

Quale delle due opzioni utilizzare? 🤔
Entrambe le sintassi sono più o meno equivalenti e producono lo stesso risultato.

Vedrai sempre più spesso che in programmazione non esiste un'unica via per risolvere un problema. Anzi, tutto il contrario. Quando troveremo modi diversi di fare le cose proverò ad evidenziare le differenze in modo tale che tu possa applicare la soluzione ottimale in base al problema che stai affrondando.

In questo caso, però, non c'è una vera e propria differenza tra usare il borderRadius ed il RoundedRectangle dato che quest'ultimo fa uso della stessa logica che il borderRadius utilizza per arrotondare i bordi.

ForegroundColor

Abbiamo visto come assegnare un colore al background. Ma come possiamo cambiare colore al Text?

La soluzione è il modifier foregroundColor. Il foregroundColor può essere applicato su elementi che contengono testo, quindi anche altri componenti rispetto al Text, e su container come VSTack e HStack per assegnare il colore a tutti gli elementi che contengono:

foregroundColor(_ color: Color?) -> View

Per esempio, nella nostra bannerView, potremmo cambiare il colore dei due Text o assegnando il foregroundColor ad ognuna di loro, oppure direttamente alla stack che li contiene:

HStack {
    Text("Modifiers")
        .font(.title)
        .fontWeight(.bold)
    Spacer()
    Text("more")
}
.foregroundColor(.white) // o Color.white è equivalente

// oppure

HStack {
    Text("Modifiers")
        .font(.title)
        .fontWeight(.bold)
        .foregroundColor(.white)
    
    Spacer()
    
    Text("more")
        .foregroundColor(.white.opacity(0.8))
}
.padding()

Nel caso in cui volessi modificare l'opacità di un Color per renderlo quindi trasparente, ti basta aggiungere il modifier opacity al colore in questione:

Color.red.opacity(0.5) // 50%

Il valore di opacity è un numero con virgola che va da 0 ad 1. 1 rappresenta 100% quindi no opacità, e l'estremo inferiore, quindi 0.0 sarà totalmente trasparente: 

func opacity(_ opacity: Double) -> Color

Double è il type che nel linguaggio Swift rappresenta valore in virgola. CGFloat, che abbiamo visto nel cornerRadius e tra poco nel modifier padding, è un valore equivalente o sinonimo del Double.
L'unica differenza sostanziale è che il CGFloat è utilizzato per operazione grafiche, infatti significa: Core Grafic Float.

Padding

Concludiamo in bellezza questa lezione parlando del modifier che ci portiamo dall'inizio del corso: padding. Il modifier padding in SwiftUI permette di distanziare i confini di una view.

Di default distanzia tutti e quattro i lati ma abbiamo la possibilità di attivare il padding su uno o più lati specificando un valore del tipo Edge.set:

padding(_ edges: Edge.Set, length: CGFloat?)

Dove i valori di Edge.Set sono:

Edge.Set.leading
.trailing
.top
.bottom
.leading
.vertical 
.horizontal
.all

Trailing e leading li abbiamo già incontrati quando abbiamo parlato di alignment per la StackView e significano trailing/destra e leading/sinistra. vertical ed horizontal permettono di modificare contemporaneamente top/bottom o leading/trailing rispettivamente.

Di default edges è sempre settato su .all.

Nel caso in cui volessi modificare solamente un lato con una distanza ben specifica, allora scriverai:

.padding(.top, 30)

Mentre, se volessi modificare più di un lato ed usare lo stesso valore, allora ti basterà mettere gli Edge all'interno di parantesi quadre:

.padding([.top, bottom], 20)

Ed infine, se volessi modificare i lati con diversi valori, nessuno ti vieta di aggiungere più di un padding alla tua view:

myView
   .padding(.horizontal, 40)
   .padding(.bottom, 20)

Se riportiamo queste nozioni al nostro progetto ed aggiungiamo una semplice VStack sotto la bannerView ed utilizziamo due padding top/bottom:

var welcomeView: some View {
    VStack(alignment: .leading, spacing: 5) {
        Text("Hi 👋")
            .font(.title2)
        Text("Giuseppe")
            .font(.title3)
    }
    .padding(.top, 20)
    .padding(.bottom, 30)
}

Esercizio

Adesso che abbiamo in canna alcuni dei modifier fondamentali lascio a te il compito di completare il layout della lezione:

Nel caso avessi avuto qualche problema, o vuoi confrontarlo con la soluzione, qui trovi il codice completo.

Conclusione

Ci siamo, adesso sappiamo che SwiftUI contiene una libreria di componenti e modifiers. Finora abbiamo incontrato componenti che si comportano da contenitore, come VStack ed HStack ed altri che rappresentano informazioni come il Text.

Tutti i componenti in SwiftUI offrono dei modifiers che ne permettono di alterarne il normale look and feel.

Nella prossima lezione, daremo un'occhiata al componente Image prima di passare alle basi della programmazione Swift per comprendere fino in fondo il codice che stiamo scrivendo.

Buona programmazione!