Asset, Image ed AsyncImage
In questa lezione del corso SwiftUI Tour aggiungeremo nella nostra cassetta degli attrezzi il componente Image
che utilizzeremo per renderizzare immagini locali (Assets) o remote tramite URL
.
Voglio dedicare un’intera lezione al componente Image
di SwiftUI, perché mi sarà utile per accennarti due sintassi del linguaggio Swift: closure
e func
che ci permetteranno rispettivamente di definire il comportamento di un component o riutilizzare codice evitando così di duplicare codice.
Alla fine di questa lezione sarai in grado di creare il seguente layout:
Let’s start!
Asset
Prima di passare al codice dobbiamo conoscere la differenza tra risorse locali e remote.
Per locali si intendono le immagini o risorse che risiedono materialmente nella memoria fisica del tuo dispositivo. Queste immagini non le troverai in galleria ma risiederanno in uno spazio dedicato alla tua applicazione.
Le immagini remote, invece, sono quelle identificate da un URL. Esempio:
https://somedomain.com/image.jpg
La cartella Assets
, che trovi nel tuo File Navigator
è la folder dedicata alle risorse locali:
Ogni progetto iOS contiene una cartella chiamata Assets
.
All’interno di questo file inserirai le immagini che serviranno al tuo layout ed i contenuti statici della tua app come icone e colori.
Il file
AppIcon
servirĂ a definire l'icona della tua app. Mentre il fileAccentColor
modificherĂ latint
di default utilizzata in elementi comeButton
e link.
Come aggiungere un asset in Xcode
Come si fa ad aggiungere nuove immagini ad un progetto Xcode?
Ti basta aprire il file Asset
, e poi trascinare l’immagine nella colonna di sinistra (dove trovi AppIcon
):
Eventualmente puoi utilizzare tasto destro nella colonna di sinistra e selezionare
New Image Set
. Un'altra alternativa è l'icona+
che trovi in fondo a sinistra in questa colonna.
Con questa operazione hai passato l’immagine dentro il progetto e, di conseguenza, potrai utilizzarla all’interno della tua app. Come? Grazie al nome o identificativo dell’asset.
Di default l’asset prende il nome del file. Puoi modificare questo nome facendo click e premendo INVIO sul file appena aggiunto.
Adesso, passiamo all’azione e vediamo come utilizzare quest’immagine all’interno della tua View.
Come usare Image in SwiftUI
Il componente che ci permetterà di renderizzare immagini all’interno di una View si chiama Image
. Se provi a creare una Image
all’interno del tuo body
vedrai che ti metterĂ a disposizione diversi costruttori.
Quello che userai per le ’immagini locali si chiama:
Image(_ name: String)
Cosa dobbiamo passare all’interno delle parenti tonde? Il nome del tuo asset messo tra doppie virgolette. Nel mio caso scriverò:
Image("peppe")
Ed ecco qui comparire l’immagine all’interno del Canvas.
Le doppie virgolette rappresentano contenuto di tipo
String
. Imparerai presto che ogni valore viene rappresentato da una parola chiamataType
che ne definisce il comportamento. Se sei curioso, puoi dare un'occhiata a questa lezione.
Image modifiers
Prima di vedere le immagini asincrone o remote, diamo prima uno sguardo ai possibili modifiers che potrai utilizzare per modificare il comportamento e look delle tue Image in SwiftUI.
Resizable ed aspectRatio
Come puoi notare l’Image
, in base alle sue dimensioni intrinseche (quelle del file), prende o meno tutto lo spazio a disposizione della View
. Ti faccio un esempio così da capire immediatamente il problema.
Prendiamo in considerazione quest’immagine. Scaricala ed aggiungila nel tuo Xcode Assets
con il nome kangaroo
:
Dato che l’immagine ha dimensioni 1200 × 628
, quando la utilizzerai all’interno della tua view, vedrai che i suoi confini andranno al di fuori della view:
Ovviamente, a meno di casi particolari, è buona norma evitare che le immagini escano dai confini della view.
Qui entrano in gioco i primi due modifiers: resizable
ed aspectRatio
.
resizable
permette all’immagine di essere ridimensionata utilizzando altri modifiers.aspectRatio
permette di ridefinire le proporzioni dell’immagine e come questa debba occupare lo spazio all’interno della view che la contiene.
Il primo è abbastanza semplice da usare, subito dopo aver creato l’Image aggiungi resizable()
senza nessun parametro all’interno delle parentsi:
Image("kangaroo")
.resizable()
Vedrai che nulla cambia rispetto a prima. Non preoccuparti, il modifier resizable
viene sempre utilizzato in congiunzione con altri modifiers. L’importante è che sia sempre il primo nella catena dei modifiers di una Image.
Adesso passiamo ad aggiungere aspectRatio
. Ci sono diverse opzioni, quelle che utilizzeremo noi saranno:
.aspectRatio(contentMode: ContentMode)
// oppure
.aspectRatio(_ aspectRatio: CGFloat?, contentMode: ContentMode)
Partiamo dalla prima con solamente il parametro contentMode
.
Questo è un valore appartenente al type ContentMode
, abbiamo visto nella lezione precedente del corso SwiftUI Tour, che alcuni type offrono dei valori di default. In questo caso le opzioni del type ContentMode
sono due: fill
e fit
.
fit
: occupa lo spazio in base al contenitore in cui si trova. Preserva l’aspect ratio e lascia spazi bianchi nel caso in cui ci fosse spazio in eccesso in uno dei due lati.fill
: occupa tutto lo spazio a disposizione e sfocia i confini se l’immagine è più grande del contenitore. Anche questa modalità preserva l’aspect ratio.
Proviamo la modalitĂ fit
(e lascio a te il compito di provare la fill
):
Image("kangaroo")
.resizable()
.aspectRatio(contentMode: .fit)
frame
Adesso che siamo in grado di ridimensionare l’immagine vediamo come dargli delle dimensioni fisse utilizzando il modifier frame
.
Prima di vederlo in azione è importante precisare che questo modifier può essere utilizzato su tutte le view.
Il modifier frame
ha diversi parametri, per evitare di scendere in troppi dettagli, quello che utilizzeremo nella maggior parte dei casi contiene solamente width
ed height
:
.frame(width: CGFloat?, height: CGFloat?) // larghezza ed altezza
.frame(width: CGFloat?) // solo larghezza
.frame(height: CGFloat?) // solo altezza
Proviamo sull’immagine definendo solamente la larghezza. CGFloat
è un valore numero, quindi proviamo con 200
:
clipped
Frame ricopre un ruolo importantissimo quando usato in combinazione con aspectRatio(contentMode: .fill)
.
Infatti utilizzando il modifier clipped
subito dopo un frame
potrai ritagliare i bordi in eccesso.
Il modifier clipped
taglia letteralmente l’immagine in base alle dimensioni della view:
Image("kangaroo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 200)
.clipped()
Nel caso in cui tu voglia arrotondare l’immagine, potrai utilizzare il modifier cornerRadius
, come abbiamo visto nella lezione sui modifier, oppure potrai utilizzare clipShape
invece del modifier clipped
.
.clipShape(shape: Shape)
Cosa passeremo come parametro? Possiamo passare uno tra:
.circle
// oppure
Circle()
// oppure
RoundedRectangle(cornerRadius: 8) // se non vuoi utilizzare il mod cornerRadius
Image("kangaroo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 200)
.clipShape(.circle)
Perfetto! per il momento fermiamoci qui con i modifier sulle immagini.
AsyncImage in SwiftUI
Dopo aver visto come utilizzare il componente Image
per renderizzare immagini locali, passiamo al componente AsyncImage
che ci permetterĂ di mostrare immagini provenienti da URL
.
Prima di entrare nel vivo di AsyncImage
dobbiamo introdurre una nuova parola del linguaggio Swift che ci aiuterĂ a rappresentare indirizzi web.
La parola da utilizzare si chiama URL
e viene costruito utilizzando la sintassi:
URL(string: "https://link.com/etc")!
Il punto esclamativo
!
alla fine delle parentesi tonde è un'operazione chiamataOptional
force-unwrap e la studieremo nel dettaglio nel prossimo modulo.
Per il momento, serve Swift per capire che l'indirizzo web passato comestring
è effettivamente un URL.
Dove dobbiamo scrivere questo URL?
Qui ritornano d'aiuto le var
che abbiamo incontrato nella lezione precedente.
Immaginiamo di voler mostrare questa immagine di Melbourne, ci basterĂ copiare il link, inserirlo all'interno di un URL(string: "incolla-link-qua")!
ed infine passare questo URL
dentro una var
:
var melbourneImageURL = URL(string: "https://media.istockphoto.com/photos/melbourne-at-dusk-picture-id493621192?k=20&m=493621192&s=612x612&w=0&h=G2D4HuVz8TbTQIOiySadIL4ZYLQO36kf8kyjuDITWqU=")!
Nel prossimo modulo del corso SwiftUI Tour capiremo nel dettaglio a cosa servono le variabili in Swift.
Adesso che abbiamo un URL
a disposizione della nostra View
non ci resta altro che darlo in pasto ad un AsyncImage
.
AsyncImage
ha diversi costruttori, quello che per il momento utilizzeremo ha queste sintassi:
AsyncImage(url: URL?)
Il punto interrogativo
?
, in un contesto di parametri, sta ad indicare la possibilità d'omettere il valore. Anche questa è un'operazione che studieremo nel prossimo modulo.
In pratica, dopo url:
possiamo passargli o un URL(string: "")!
o il nome di una variabile che contiene un URL
. Nel nostro caso, passeremo melbourneImageURL
:
struct ContentViewTutorial: View {
var melbourneImageURL = URL(string: "https://media.istockphoto.com/photos/melbourne-at-dusk-picture-id493621192?k=20&m=493621192&s=612x612&w=0&h=G2D4HuVz8TbTQIOiySadIL4ZYLQO36kf8kyjuDITWqU=")!
var body: some View {
AsyncImage(url: melbourneImageURL)
}
}
Quando AsyncImage
verrĂ mostrata all'interno della view, scaricherĂ il contenuto dell'URL
e lo inserirĂ all'interno di una Image
.
Loading indicator AsyncImage
AsyncImage
data la sua natura asincrona ci permette di definire una View
da utilizzare durante il download dell'immagine. La sintassi per definire una loading bar si complica leggermente, infatti dovremmo scrivere:
AsyncImage(url: melbourneImageURL) { image in // 1
// qua modificheremo l'Image
image
} placeholder: { // 2
// qua definiremo la loading bar
}
All'interno del blocco
{ image in }
potremo modificare l'Image
utilizzando i modifiers visti poco fa.All'interno del blocco
placeholder: {}
invece potremo definire unaView
che verrĂ mostrata durante il caricamento dellaAsyncImage
.
Proviamo ad inserire un Text
come placeholder
. Una volta fatto, ti accorgerai che per qualche piccolo istante quel Text
verrĂ mostrato prima che l'immagine contenuta nell'URL
viene scaricato:
AsyncImage(url: melbourneImageURL) { image in
image
} placeholder: {
Text("Caricamento...")
}
Nel caso in cui volessi modificare la Image, ti basterĂ farlo all'interno del blocco { image in }
lì ti basterà aggiungere i modifier alla parola image
subito dopo in
:
AsyncImage(url: melbourneImageURL) { image in
image
.resizable()
.clipped()
} placeholder: {
Text("Caricamento...")
}
Closure
Questi due blocchi di codice definiti da parentesi graffe, prendono il nome di closure
. Una closure è una funzione quale comportamento viene definito durante l'utilizzo.
Di solito, un parametro che accetta una closure ha sintassi () -> SomeType
o (SomeValue) -> SomeType
.
Quando trovi una closure
, se ha un parametro potrai scrivere:
{ nomeVar in
// fai qualcosa con nomeVar
}
Mentre senza parametro ti basteranno solamente un paio di {}
.
Nel prossimo modulo capiremo meglio questa sintassi e come utilizzarle.
ProgressView
Nel caso in cui volessi utilizzare l'animazione classifica di caricamento di iOS, invece di Text("Caricamento...")
ti basterĂ usare il componente ProgressView
:
Infine definiamo un'altezza di 200
sul frame
della AsyncImage
e del backgroundColor
. Spostiamo la AsyncImage
dentro una VStack
ed ecco che otteniamo una card opacizzata che non appena finisce il download mostrerĂ l'immagine contenuta nell'URL:
VStack {
AsyncImage(url: melbourneImageURL) { image in
image
.resizable()
.clipped()
} placeholder: {
ProgressView()
}
.frame(height: 200)
.frame(maxWidth: .infinity)
.background(Color.gray.opacity(0.3))
.cornerRadius(8)
}
.padding()
Uno sneak peak sulle funzioni
CapiterĂ di dover inserire piĂą immagini o in generale componenti che si ripetono in look and feel ma cambiano solamente di contenuto. In questi casi, possiamo sfruttare un'altra feature del linguaggio Swift: le funzioni.
Una funzione è un codice che permette di trasformare un input in un output (Entreremo nei dettagli delle funzioni in Swift in una lezione dedicata all'argomento).
La sintassi di base è:
func nomeFunzione(nomeParametro: InputType) -> OutputType {
return OutputType
}
func
è la keyword dedicata del linguaggio Swift.nomeFunzione
rappresenta ciò che la funzione farà /creerà . Puoi mettere qualsiasi nome l'importante è che cominci per minuscolo ed non abbia spazi.(nomeParametro: InputType)
dentro le parantesi definiremo gli input della funzione. Possono anche essere omessi ma le parentesi vanno messe comunque.-> OutputType
cosa restituirà la funzione. Può essere omesso e vedremo più avanti qualche esempio.{}
dentro le parentesi definiremo il comportamento della funzione.return
è la keyword che ci permetterà di uscire dalla funzione e restituire un output a chi utilizza la funzione.
Esempio. Immagina di creare un ricettario ed hai tanti nomi di ricette "Carbonara", "Matriciana" etc che vuoi mostrare in una View sotto forma di Text
. Invece di copiare ed incollare X volte Text
:
Text("Carbonara")
.font(.title2)
.fontWeight(.bold)
Text("Matriciana")
.font(.title2)
.fontWeight(.bold)
Text("Cotoletta alla milanese")
.font(.title2)
.fontWeight(.bold)
Potremmo definire una func
che prende in input un String
ed restituisce in output un Text
:
func recipe(name: String) -> Text {
return Text(name)
.font(.title2)
.fontWeight(.bold)
}
Aggiungi la funzione sotto la var body
.
Infine, per utilizzarla ti basterĂ scrivere il nome della func e per ogni parametro puoi passare un valore del tipo che si aspetta:
Esercizio
Capito piĂą o meno come strutturare una funzione, adesso passiamo ad un esempio piĂą complesso sfruttando le AsyncImage
. In questo caso te lo lascio come esercizio.
Il tuo obiettivo è ricreare il seguente layout (sotto trovi dei tips):
Aggiungi queste quattro variabili all'interno della tua view:
var etnaImageURL: URL = URL(string: "https://static.independent.co.uk/2021/02/25/16/APTOPIX_Italy_Etna_Volcano_Eruption_91254.jpg?quality=75&width=982&height=726&auto=webp")!
var melbourneImageURL: URL = URL(string: "https://media.istockphoto.com/photos/melbourne-at-dusk-picture-id493621192?k=20&m=493621192&s=612x612&w=0&h=G2D4HuVz8TbTQIOiySadIL4ZYLQO36kf8kyjuDITWqU=")!
var taorminaImageURL: URL = URL(string: "https://www.thethinkingtraveller.com/media/Resized/SICILY%20local%20areas/Taormina_and_beaches/1920/Taormina%20(1).jpg")!
var sydneyImageURL: URL = URL(string: "https://a.cdn-hotels.com/gdcs/production0/d1645/0c67ff64-cf54-4886-91dd-89aa601463af.jpg")!
Per evitare di dover copiare ed incollare la
AsyncImage
crea una funzione che prende in input unURL
e restituisce unasome View
:
func myCustomImage(url: URL) -> some View {
// aggiungi qui la AsyncImage che usa url come valore
}
-> some View
ti permette di definire un output generico, l'importante è che sia unaView
. Poi utilizzamyCustomImage
all'interno della view.Nel caso in cui volessi far scrollare le immagini puoi metterle all'interno di una
ScrollView
:
ScrollView(showsIndicators: false) {
myCustomImage(etnaImageURL)
myCustomImage(melbourneImageURL)
myCustomImage(taorminaImageURL)
myCustomImage(sydneyImageURL)
}
Fammi sapere nei commenti se hai avuto qualche problema. Nel caso in cui volessi confrontare il tuo progetto con il mio, lo trovi qui.
Conclusione
In questo tutorial abbiamo visto come il componente Image
ed AsyncImage
ci aiutano a mostrare immagini locali e remote.
Abbiamo anche dato un'occhiata superficiale alle funzioni e come queste ci permettono di ridurre codice duplicato oltre ad organizzare un po' meglio le nostre view.
A breve concluderemo questo primo modulo sulle funzionalitĂ base di SwiftUI e ci addentreremo un po' meglio nei concetti del linguaggio Swift. Infatti finora abbiamo usato concetti di programmazione come var
, func
, String
etc senza però capire fino in fondo cosa sono.
Ma non preoccuparti, presto tutto avrĂ un senso!
Se hai avuto qualche problema, al solito, scrivimi un commento qui o sui social.
Buona programmazione!