Variabili e State in SwiftUI
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.
👋 Il video contiene sia le nozioni di questa lezione che sulla lezione State e Binding. L'esercizio ed il codice finale lo trovi nella prossima lezione.
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:
Esplicita: Il type associata alla
var
viene definito scrivendolo manualmente.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 utilizzarelet
quando le tuevar
non vengono modifiche. Xcode ti aiuterà a capire quandolet
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 euroed un
Text
che visualizza ilbalance
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 uninit
con secondo parametroformat
cui puoi passare diversi formati di rappresentazione. In questo caso ho passatodateTime
perché ho utilizzato un oggetto di typeDate
.
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 typeDouble
. Altrimenti puoi usare la string interpolationText("\(balance)")
ma perderai la formattazione usata dalformat
.💡 Nota come ho sommato i due
Text
. Infatti gli oggettiText
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
}
Il primo accetta una
LocalizedStringKey
o, per il momento, un qualsiasi oggettoString
come primo argomento. Mentre il secondo argomento è una funzione{}
dove all'interno scriveremo il codice da eseguire al tap.Il secondo costruttore invece ha come primo argomento la funzione e come secondo una
closure
chiamatalabel:
che ci permette di definire una qualsiasiView
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
etrash
.
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!