Per il linguaggio Swift, una variabile è un contenitore malleabile d'informazioni. Ovvero, una volta che una var viene creata questa può cambiare il suo contenuto durante il corso d'esecuzione del programma.

Per esempio, in app come Uber Eats, Deliveroo etc quando aggiungi ed elimini un prodotto dal carrello, dietro le quinte stai modificando il contenuto di una variabile.

Quando non avrai bisogno di mutabilità, ma avrai semplicemente bisogno di un posto dove conservare delle informazioni statiche, il linguaggio Swift ti offre un contenitore costante chiamato let.

var e let sono le keyword che definiscono lo storage di base di qualsiasi informazione manipolata all'interno di un'app. Che sia un input, una costante di moltiplicazione, il titolo della tua app, il valore del tuo conto in banca, tutto prima o poi passerà all'interno di una variabile o costante.

In questa lezione vedremo come si definiscono variabili e costanti in Swift e poi vedremo come queste si possono utilizzare in SwiftUI per catturare user input e cominciare a creare logiche più complesse.

var in Swift

Una variabile la puoi immaginare come una scatola in cui potrai inserire e rimuovere informazioni. Le informazioni che potrai conservare dipendono dal Type che assocerai ad una var.

Infatti, Swift è un linguaggio type-safe, ed una caratteristica è quella di calcolare a compile time il type o tipologia di contenuto di una variabile.

Tradotto significa che quando creerai una var definirai anche il Type degli oggetti che potrà trasportare.

Adesso passiamo alla pratica.

Per definire una variabile in Swift dovrai utilizzare una delle seguenti sintassi:

  1. Esplicita: Il type associata alla var viene definito scrivendolo manualmente.

  2. Implicita: Il type viene dedotto dal contenuto passato durante la creazione della var.

var explicitVar: Int = 2 // 1
var implicitVar = "ciao"  // 2

Analizziamo la sintassi.

La var è la keyword che permette a Swift di capire che stai definendo una variabile. Subito dopo va il nome della variabile. Immagina questo nome come l'etichetta da associare alla scatola.

Il nome delle var va sempre definito in minuscolo, senza spazi e deve cominciare con un carattere (quindi no numeri o caratteri speciali).

Puoi utilizzare la sintassi camelCase o snake_case, in linea generale nel mondo Swift si preferisce la camelCase.

Nel caso di variabili esplicite, subito dopo il nome si mettono i due punti seguiti dal nome del type che potrai conservare : Type.

Il simbolo = in informatica ha un significa diverso da quello matematico. Infatti, l'operazione viene chiamata assegnazione. Nel nostro esempio, alla variabile explicitVar abbiamo assegnato il valore 2 mentre alla variabile implicitVar abbiamo assegnato l'oggetto "ciao".

All'interno di un blocco di codice, le variabili o costanti devono avere nome univoco.

let in Swift

Le costanti si definiscono utilizzando la stessa sintassi delle variabili. L'unica differenza è la keyword utilizzata.

Per creare una costante in Swift si utilizza la parola let. Anche qui puoi utilizzare la sintassi implicita ed esplicita:

let explicitLet: Bool = false
let implicitLet = 5.99

La differenza tra var e let è che quest'ultima non potrà essere modificata.

Utilizzare var e let

Una volta definita una variabile o costante, potrai utilizzarle all'interno del tuo codice scrivendo il loro nome.

Per esempio, ipotizziamo d'avere due variabili che rappresentano il nostro conto in banca ed un ipotetico deposito. Vogliamo aggiungere il deposito al nostro conto, quindi scriveremo:

var balance: Double = 0
let deposit = 10.0

balance + deposit 

Nota come nell'esempio ho utilizzato let per il deposit, dato che il deposit non cambierà non ha senso definire quello storage come variabile.

Utilizzare var ha un drawback in termini di performance. Infatti Swift deve tener conto delle possibili modifiche. In linea di massima, prova sempre ad utilizzare let quando le tue var non vengono modifiche. Xcode ti aiuterà a capire quando let può essere un'alternativa a var.

Inoltre, quando utilizziamo le var o let in operazioni, il linguaggio Swift apre la scatola ed utilizza il suo contenuto. Infatti, agli occhi di Swift quella riga balance + deposit è equivalent a 0 + 10.0 (che sono i valori definiti sopra).

Un concetto da afferrare in programmazione è che le operazioni non modificano i contenuto delle variabili su cui vengono performate. Nell'esempio sopra l'operazione balance + deposit non ha modificato il contenuto della var balance.

Infatti, se proviamo a vedere il contenuto di balance subito dopo l'operazione, noteremo come il suo valore è ancora 0:

balance + deposit // 10

balance // 0

Come risolviamo questo problema?

Qui torna in aiuto l'operatore di assegnazione =. Infatti, ogni qual volta ti servirà passare o assegnare il valore di un'operazione potrai utilizzare il simbolo uguale:

balance = balance + deposit

In questo caso stiamo assegnando il valore 0 + 10 alla var balance.

Proviamo ad eseguire un'altra operazione. Questa volta eseguiamo un prelievo, e ricordiamoci di modificare il balance:

let withdraw = 2.5

balance = balance - withdraw

Attenzione alle sottrazione di valori negativi. Come in matematica, la doppia sottrazione genera un'addizione. Nell'esempio se avessi definito withdraw = -2.5 e poi avessi eseguito balance - withdraw il risultato sarebbe stato 10 -1 * -2.5 che equivale a 10 + 2.5.

Provare per credere!

Type safe

Abbiamo già visto che Swift è un linguaggio ad oggetti ed una delle sue proprietà è quello di essere type-safe. Qui voglio solo ribadire il concetto e vederlo in pratica con le variabili e costanti.

Essere type-safe significa che quando una var ha un type definito questo non potrà essere cambiato. Inoltre, le sue operazioni saranno delimitate dal type che lo rappresenta.

Per esempio, non potrò sommare una String ad una var Double e così via:

let name: String = "Giuseppe"
let age: Int = 30

let description = name + ", age: " + age // Error

Infatti l'operazione fallisce con un errore: Binary operator '+' cannot be applied to operands of type 'String' and 'Int'.

Come possiamo rimediare al problema?

O cambi il type della costante age o dovrai convertire il suo valore durante l'utilizzo. Vediamo quest'ultima:

let description = name + ", age: " + String(age)
// "Giuseppe, age: 30"

String interpolation

Sommare oggetti di type String sarà un'operazione super ricorrente all'interno dei tuoi progetti ed scrivere varA + " " + varB non è di certo la migliore delle soluzioni.

Il linguaggio Swift, per fortuna, offre un'operazione chiamata String Interpolation che ci permette di inserire un valore all'interno di una stringa utilizzando la notazione "\(nomeVar)" o "\(valore)".

L'esempio sopra si trasforma in:

let description = "name: \(name), age: \(age)"

Nota come non ho dovuto utilizzare la Int(age). Infatti, durante un'operazione di String Interpolation, Swift esegue una conversione da qualsiasi Type in String.

Computed property

Capiterà spesso che vorrai creare delle var che calcoleranno il loro valore in base al contenuto di altre variabili.

Un esempio è se volessimo aggiungere all'interno della description il balance del nostro conto in banca. Anche convertendo da let a var dovremo aggiornare il valore della description ogni qual volta modificheremo il balance. Altrimenti sarà fuori sincrono rispetto ai dati reali:

var description = "name: \(name), age: \(age), balance: \(balance)"

balance = balance + 5

description = "name: \(name), age: \(age), balance: \(balance)"

Assegnare quella stringa alla description ad ogni singola modifica di balance è sintomo di un problema e potrebbe non essere fattibile in grossi progetti.

Qui entrano in gioco le computed-property.

Una computed property è una variabile in grado di eseguire del codice prima di restituire il suo valore. La sintassi è abbastanza intuitiva:

var nameVar: Type {
   // codice
   return Type
}

Dopo aver specificato il type della var basterà aggiungere le parentesi graffe {}. All'interno delle quali inserire un codice, se necessario, ed infine un'istruzione return seguita da un oggetto del type che la var si aspetta.

Per esempio, nel caso di description scriveremo:

var description: String {
    "name: \(name), age: \(age), balance: \(balance)"
    // equivalente di 
    // return "name: \(name), age: \(age), balance: \(balance)"
}

Tip: quando hai solamente un'istruzione puoi omettere la keyword return.

Adesso, quando utilizzerai la description dopo aver modificato il balance il suo valore verrà calcolato sul momento:

Le computed properties definite in questo modo non possono essere modificate. Vedremo più avanti che esistono altre tipologie di computed properties che potranno accettare valori.

Ad ogni modo, tu hai già utilizzato computed property. Dove?

In SwiftUI.

La var body: some View è una computed property. Oppure abbiamo usato queste computed property per spezzare l'interfaccia in piccole parti.

E dato che stiamo parlando di SwiftUI, apri un progetto e vediamo nel dettaglio come usare le var e let.

Variabili e State in SwiftUI

In SwiftUI le variabili sono legate ad una funzionalità di SwiftUI che permette di refreshare l'interfaccia ad ogni loro modifica.

Per esempio, se la nostra UI rappresenta il conto in banca ed abbiamo:

  • due Button per aggiungere/rimuovere 50 euro

  • ed un Text che visualizza il balance

Modificare la var balance tramite i bottoni farà in modo che SwiftUI automaticamente aggiorni il contenuto del Text.

Capiamo nel dettaglio il funzionamento.

State in SwiftUI

SwiftUI è un framework dichiarativo, ovvero le UI o View vengono create ed aggiornare in funzione dello stato che rappresentano. Ovvero, cambiamenti di stato o di valore verranno automaticamente riflesse nella user interface.

Per stato di una View si intendono i valori che ne ne rappresentano il contenuto. Lo stato di una view può essere variabile o costante.

Stato costante

Nel caso di stato costante, questo viene espresso utilizzando delle semplici let. Per esempio, se volessimo mostrare in un Text la data odierna potremmo scrivere:

let today: Date = Date.now

var body: some View {
    VStack {  
        Text(today, format: .dateTime)
    }
}

💡 Text ha un init con secondo parametro format cui puoi passare diversi formati di rappresentazione. In questo caso ho passato dateTime perché ho utilizzato un oggetto di type Date.

Anche se avessimo potuto scrivere Text(Date.now, format: .dateTime), avere una let al di fuori del body in alcuni casi ci permette di organizzare meglio il nostro codice.

In altri casi avere le let al di fuori del body sarà necessario perché le utilizzeremo per conservare valori passati da altre view.

Stato variabile

In SwiftUI qualsiasi variabile che verrà modificata dalla UI deve essere preceduta dal tag @State.

@State var someInt: Int = 0

@State permette a SwiftUI di seguire i cambiamenti di questa var ed automaticamente aggiornare tutti i pezzi di UI che ne fanno riferimento.

Infatti SwiftUI è intelligente abbastanza da capire quali sono i componenti delle tue interfacce che utilizzano State properties ed eseguire un refresh lasciando inalterati gli altri componenti.

Da qui in avanti, useremo @State var per tutte le variabili che accetteranno input o verranno modificate direttamente o indirettamente dai nostri utenti.

Per ritornare all'esempio del conto in banca, aggiungiamo alla nostra ContentView una nuova @State balance property:

struct ContentView: View {
    
    @State var balance: Double = 0

Infine, aggiungiamo all'interno del Body un Text che ne rappresenta il valore:

var body: some View {
    VStack {     
        Text(today, format: .dateTime)
        
        Text("Balance: ")
            .font(.title)
        +
        Text(balance, format: .number)
            .font(.title)
            .fontWeight(.bold)    
    }
}

💡 Per il balance ho usato il format .number dato che l'oggetto è di type Double. Altrimenti puoi usare la string interpolation Text("\(balance)") ma perderai la formattazione usata dal format.

💡 Nota come ho sommato i due Text. Infatti gli oggetti Text credo siano gli unici oggetti di SwiftUI che permettono la somma tra loro.

Adesso vediamo come modificare il balance usando il componente Button.

Button

In questa lezione introduciamo il primo componente interattivo: il Button.

I costruttori del Button più famosi sono generalmente due:

Button("un titolo") { // 1
   // codice da eseguire al tap
}

Button { // 2
   // codice da eseguire
} label: {
   // una View
}
  1. Il primo accetta una LocalizedStringKey o, per il momento, un qualsiasi oggetto String come primo argomento. Mentre il secondo argomento è una funzione {} dove all'interno scriveremo il codice da eseguire al tap.

  2. Il secondo costruttore invece ha come primo argomento la funzione e come secondo una closure chiamata label: che ci permette di definire una qualsiasi View come label del button.

Vediamo un esempio:

HStack {
    Button("Add to balance") {
        balance = balance + 10
    }
    
    Button {
        balance = balance + 10
    } label: {
        HStack(spacing: 0) {
            Image(systemName: "dollarsign.square.fill")
            Text("Add")
        }
    }
}
.padding(.top)

Abbiamo incontrato le closure, nella lezione dedicata al componente AsyncImage. Entreremo nei dettagli di questa sintassi tra qualche lezione.

Nel caso in cui volessi modificare lo style del button puoi utilizzare il modifiers buttonStyle() passando uno degli style forniti da PrimitiveButtonStyle a cui puoi accedere utilizzando i valori di default (quindi mettendo .): automatic, bordered, borderedProminent, borderless e plain.

Lascio a te vedere cosa fa ognuno di questi, noi utilizzeremo .borderedProminent. Pro tip, se hai più bottoni nella tua UI e vuoi dare lo style a tutti, metti questo modifiers nel contenitore più esterno:

HStack {
    // Buttons qui
}
.padding(.top)
.buttonStyle(.borderedProminent)

Infine, nel caso in cui volessi avere un bottone in rosso per indicare un'operazione di cancellazione o con effetti negativi, puoi costruire un bottone passando .destructive all'argomento role:

Button("Remove", role: .destructive) {
  balance = balance + 10
}

// Oppure
Button(role: .destructive) {
} label: {
}

Infine, ecco il codice completo che riproduce l'esempio mostrato ad inizio del paragrafo:

struct ContentView: View {
    
    @State var balance: Double = 0
    
    let today: Date = Date.now
    
    var body: some View {
        VStack {
            
            Text(today, format: .dateTime)
            
            Text("Balance: ")
                .font(.title)
            +
            Text(balance, format: .number)
                .font(.title)
                .fontWeight(.bold)
            
            HStack {
                Button("Add to balance", role: .destructive) {
                    balance = balance + 10
                }
                
                Button(role: .destructive) {
                    balance = balance + 10
                } label: {
                    HStack(spacing: 0) {
                        Image(systemName: "dollarsign.square.fill")
                        Text("Add")
                    }
                }
            }
            .padding(.top)
            .buttonStyle(.borderedProminent)
        }
    }
}

Esercizio: Semi calcolatrice

Adesso che abbiamo nel nostro repertorio var e let ed il componente Button posso cominciare ad assegnarti degli esercizi un po' più complessi.

Il problema di oggi consiste nel creare una pseudo calcolatrice dove l'utente può selezionare due numeri ed eseguire l'operazione di addizione, sottrazione, moltiplicazione e divisione tra i due.

Per selezionare i due numeri, crea due righe di bottoni da 1 a 5. La riga superiore seleziona il primo numero e dalla seconda riga si seleziona la seconda. Poi aggiungi una terza riga di bottoni con cui eseguire le operazioni.

Poi aggiungi un bottone reset per resettare le tre variabili.

Ecco un video esempio:

Tips:

  • Ti serviranno almeno tre var.

  • Per le icone dei bottoni utilizza Image(systemName: String) come costruttore e usa i valori: plus, minus, multiply, divide e trash.

Nel caso in cui avessi avuto dei problemi, scrivimi sotto ed eventualmente guarda il codice completo qui.

Conclusione

Che sia un'app con logiche più o meno complessa, avrai sempre a che fare con variabili e costanti.

Le regole sono abbastanza semplici, stati che necessitano input o modifiche vanno conservati in var o @State var se queste si trovano all'interno di una View.

Inoltre var e let hanno sempre bisogno di un Type di riferimento. Essendo Swift un linguaggio type-safe questi contenitori possono manipolare solo oggetti di un type ben definito.

Nella prossima lezione ritorneremo a parlare di Type e capiremo un po' meglio cosa significa la sintassi some View, cos'è quella struct ContentView: View e più in generale capiremo come creare nuovi Type e nuove View.

Se hai avuto qualche problema, al solito, scrivimi un commento qui sotto.

Buona programmazione!