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:

Image ed AsyncImage SwiftUI

Let’s start! 

Asset

Prima di passare al codice dobbiamo conoscere la differenza tra risorse locali e remote. 

  1. 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. 

  2. 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 file AccentColor modificherĂ  la tint di default utilizzata in elementi come Button 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 chiamata Type 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.

  1. resizable permette all’immagine di essere ridimensionata utilizzando altri modifiers.

  2. 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

  1. 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.

  2. 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:

frame modifier image swiftui

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)
clipShape image swiftui

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 chiamata Optional force-unwrap e la studieremo nel dettaglio nel prossimo modulo.
Per il momento, serve Swift per capire che l'indirizzo web passato come string è 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
}
  1. All'interno del blocco { image in } potremo modificare l'Image utilizzando i modifiers visti poco fa.

  2. All'interno del blocco placeholder: {} invece potremo definire una View che verrĂ  mostrata durante il caricamento della AsyncImage.

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 un URL e restituisce una some 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 una View. Poi utilizza myCustomImage 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!