View
logo
logo

Contatti

Labs Sviluppo Software 1 Ottobre 2020

Sviluppo App in SwiftUI

Writen by odc-admin

comments 0

Sviluppo App iOS SwiftUI Livorno

Durante il WWDC del 2019 Apple ha presentato la sua versione di un linguaggio di sviluppo app dichiarativo sulla falsa riga di React. Ovviamente basato su Swift e con la principale caratteristica di essere veloce, fluido e sopratutto utilizzabile su tutte le sue piattaforme. Lo stesso codice scritto in SwiftUI può essere utilizzato quindi su iOS, iPadOS, WatchOS e MacOS.

SwiftUI nasce per la costruzione di interfacce utente in maniera semplice, veloce e soprattuto uniforme tra tutte le diverse piattaforme Apple. Lo stesso componente infatti verrà renderizzato e si comporterà automaticamente nella maniera più appropriata a seconda del dispositivo dove viene utilizzato. Questo permetterà a chi lavora nello sviluppo app di preoccuparsi meno dei diversi aspetti di ciascuna piattaforma (ovviamente alcune distinzioni andranno sempre fatte, ci sono cose che si possono fare su MacOS mentre su WatchOS no ad esempio) e focalizzarsi maggiormente sulle funzionalità delle proprie applicazioni.

SwiftUI si basa su due principi fondamentali: una sintassi dichiarativa e un nuovo sistema per l’aggiornamento delle informazioni che dovranno aggiornare l’interfaccia. 

Differentemente da quanto accade normalmente in UIKit, invece di raccogliere gli eventi e decidere come modificare l’interfaccia utente con una sintassi dichiarativa, è necessario descrivere a priori che cosa dovrà fare l’interfaccia e come dovrà comportarsi. Al variare dei dati l’interfaccia semplicemente si aggiornerà per riflettere tali cambiamenti. Come scritto in uno dei primi esempi forniti da Apple, sarà possibile scrivere che volete visualizzare una lista di elementi formata da una label per il titolo e una per il sottotitolo, descrivere la font, l’allineamento e il colore per ciascun componente. 

Queste informazioni rappresentano la descrizione di ciascuna View, che verrà renderizzata e rinfrescata automaticamente da SwiftUI al variare delle condizioni di stato definite. Lo stato infatti è il cuore centrale di una View, quello che Apple definisce Source of Truth (fonte della verità), ciascuna View deve avere almeno un oggetto che funga da Source of Truth e che ne scateni gli aggiornamenti automatici (a meno di non voler disegnare una view completamente statica che è una casistica plausibile). Ad esempio uno Switch (chiamato Toggle in SwitftUI) varierà automaticamente il suo stato “grafico” da On a Off al variare di una variabile booleana a cui è agganciato e viceversa, quindi modificare a codice la stato della variabile porterà SwiftUI a ridisegnare la view per aggiornare lo switch mentre l’utente che manualmente effettua un tap sul componente aggiornerà il valore della variabile a cui è agganciato.

Questo ci porta al secondo principio su cui si basa SwiftUI, Combine un nuovo framework, introdotto sempre durante il WWDC del 2019, che come SwiftUI si prefigge di descrivere in modo dichiarativo come i valori vengano modificati nel corso del tempo. Scopo di questo articolo non è approfondire i concetti dietro Combine, che può essere usato anche con il buon vecchio UIKit, anche perché fortunatamente Apple ha già reso compatibile con Combine numerosi framework di base.

Sviluppo app in SwiftUI: iniziamo!

Dopo questa lunga introduzione credo che il modo migliore per capire come funzioni SwiftUI sia lavorare insieme allo sviluppo app di una semplice applicazione e provare a capire come si costruiscono e si possa interagire con le view. Ed essendo programmatori ho pensato di lavorare sullo sviluppo app che ci permetta di visualizzare e scegliere il nostro carburante preferito, il caffè. Essendo io un amante del caffè di Starbucks ho pensato di raccogliere le informazioni presenti su alcuni dei loro prodotti, presenti sul sito ufficiale, e creare una piccola App che mi permetta di visualizzare i caffè e scegliere i miei preferiti. Per l’occasione ho preparato una lista in json di alcuni esempi di caffè e le relative immagini da associare.

Scaricate il file zip con gli asset e siamo pronti a partire: DevelopersFuel Materiale 

Iniziate aprendo Xcode, create un nuovo progetto e selezionate Single View App. 

La prima App in SwiftUI_01

Nella schermata successiva come product name scrivete DeveloperFuel facendo attenzione a scegliere SwiftUI come User Interface. Rimuovete dalle spunte Use core data, Include Unit Tests e Include UI Tests.

La prima App in SwiftUI_02

Se è la prima volta che provate ad usare un template per SwiftUI noterete che l’interfaccia è notevolmente diversa dai progetti UIKit a cui siete abituati. Infatti la parte sinistra di Xcode è riempita da una vasta porzione che mostra in tempo reale un preview della vostra applicazione, e quando dico in tempo reale intendo che man mano che scriverete il codice l’interfaccia del preview si aggiornerà e, viceversa, aggiungendo componenti al preview il codice viene aggiornato di conseguenza. 

La prima App in SwiftUI_03

A questo punto aprite lo zip con il materiale che avete precedentemente scaricato, troverete una cartella Images che contiene le immagini da visualizzare dei nostri preziosi caffè. Trascinate tutte le immagini all’interno dell’Asset Catalog di default. 

All’interno della cartella Model troverete 3 files: BundleJsonHelper.swift, Coffee.swift e coffees.json, aggiungete anche questi tre files al progetto Xcode e siamo pronti per iniziare. Si tratta di un file json che contiene il nostro modello dati iniziale, la definizione della classe Coffee e un helper per semplificare il caricamento del file json all’interno del modello dati. 

Aprite il file Coffee.swift e date un occhiata a come sarà strutturato il nostro modello: ci sono 2 oggetti CoffeeType e Coffee, il primo raggrupperà un certo numero di caffè. Per ogni caffè sono definite alcune proprietà come nome, descrizione, foto e ingredienti. Prestate particolarmente attenzione alle ultime righe del file, viene creata un istanza statica chiamata CoffeeExample di un oggetto Coffee, questo risulterà fondamentale durante la costruzione delle nostre view.

SwiftUI nasce intorno all’idea della composizione e riusabilità delle singole View, invece di descrivere un’interfaccia complessa nella sua interezza è preferibile (e sopratutto molto più leggibile) scomporla in tanti piccoli blocchi atomici da comporre a nostro piacimento ed eventualmente riutilizzare in altre parti dell’App. La nostra App dovrà per prima cosa presentare una lista di diversi tipi di caffè (altrimenti cosa mi sono messo a copiare in file json tutte quelle informazioni dal sito di Starbucks?!?), e per fare questo per prima cosa abbiamo bisogno di rappresentare un singolo elemento di questa lista, che per semplicità chiameremo CoffeeRow.

Create un nuovo file da aggiungere al progetto, e dalla sezione User Interface selezionate SwiftUI View. Chiamate il file CoffeeRow e proseguite:

La prima App in SwiftUI_04

La prima di cui abbiamo bisogno è una proprietà che contenga i dati da visualizzare, aggiungete quindi subito dopo la definizione della struct:

var coffee: Coffee

Immediatamente il compilatore si arrabbierà indicando che manca il parametro coffee all’interno del preview. Del preview? Si esatto, perchè i preview in tempo reale che vedete sulla sx non sono altro che strutture particolari di SwiftUI che vengono compilate e renderizzate mentre modificate il codice. In alcuni casi, quando le modifiche sono troppo sostanziose per essere calcolate durante la scrittura del codice, il preview viene messo in pausa automaticamente, una volta terminate le modifiche è possibile farlo ripartire premendo il pulsante resume in alto a sinistra oppure option+command+p sulla tastiera.

Risolviamo subito il problema modificando la riga di errore da CoffeeRow() in CoffeeRow(coffee: Coffee.coffeExample). Questa è l’istanza statica che avete notato nel modello dati precedentemente. Non appena modificata la dichiarazione l’errore scomparirà e il preview si aggiornerà mostrando il confortante Hello World!, se questo non dovesse succedere avviate a mano il resume del preview.

Una view con il testo hello world però non è la più utile delle view, iniziamo quindi a mostrare alcuni dettagli che possono essere utili ai nostri utenti (o noi stessi) quando visualizziamo una lista di caffè tra cui scegliere.

Il componente principale di ciascuna View in SwiftUI è la variabile body, che deve necessariamente ritornare un oggetto di tipo View. Questa è quella che rappresenterà la nostra interfaccia utente a runtime. É importante notare che deve ritornare un singolo oggetto View, quindi non è possibile ad esempio inserire un secondo oggetto Text() sotto a quello presente, perchè appunto sarebbero 2 view. 

Ricordate il concetto di composizione di cui abbiamo parlato prima? Gli oggetti possono essere però contenuti in altri oggetti, e per questo abbiamo dei contenitori che ci permettono di raggruppare i nostri elementi di interfaccia… gli Stack!

Iniziamo creando uno stack verticale, racchiudendo il nostro testo in esso:

VStack {
    Text(“Hello World”)
}

Non un grande cambiamento per adesso ma questo è solo il primo passo. Adesso è giunto il momento di visualizzare qualcosa di vero, sostituiamo quindi il contenuto di Text con Text(coffee.name). Immediatamente nel preview la scritta Hello World! verrà sostituita con Caffè Mocha, proprio il nome del caffe che abbiamo inizializzato durante la creazione del nostro preview.

Si inizia già ad intravedere le potenzialità di SwiftUI ma ancora la nostra interfaccia non è ne carina ne esaustiva. Proviamo ad aggiungere la descrizione sotto il nome, aggiungiamo quindi un’altra Text sotto quella già presente e stavolta mostriamo la descrizione. 

var body: some View {
     VStack {
       Text(coffee.name)
       Text(coffee.description)
    }
  }

A questo punto la nostra preview dovrebbe essersi aggiornata con il titolo e la descrizione del caffe, perfettamente centrate una sotto l’altra… centrate? Avete mai visto una lista con titolo e sotto titolo centrate? Questo perchè di default un VStack ha un allineamento centrato, ma ovviamente è possibile modificare questa impostazione molto semplicemente.

Per farlo vi mostrerò un’altro modo di interazione con l’interfaccia di Xcode, tenendo premuto option cliccate su VStack nell’editor e dal menu contestuale selezionate Show SwiftUI Inspector:

La prima App in SwiftUI_05

Si aprirà l’inspector con le proprietà per lo stack verticale, un ottimo modo per scoprire che cosa sia possibile personalizzare e modificare per ciascun componente. Modificando le impostazioni di allineamento (alignment) sia il codice che il preview si aggiorneranno, mostrando i cambiamenti scelti. 

La prima App in SwiftUI_06

Selezionate leading e chiudete. Adesso è molto meglio!

Iniziamo adesso ad applicare un po’ di stile alla nostra cella, in SwiftUI si fa aggiungendo dei modificatori (view modifiers appunto) a ciascuna view. Ogni tipologia di view ha i suoi modificatori particolari, più ovviamente quelli ereditati dalle classi padre, in particolare per le nostre textview sarà possibile modificare font, colore, numero di linee, etc…

Per prima cosa modifichiamo il nome del caffè perché risalti di più, cambiando la sua font da body (la standard) a headline:

Text(coffee.name)
     .font(.headline)

E allo stesso tempo rendiamo meno importante la descrizione limitandone anche il numero di linee visualizzate:

Text(coffee.description)
     .font(.subheadline)
     .foregroundColor(.secondary)
     .lineLimit(2)

I view modifiers possono essere concatenati tra di loro, ma attenzione perché l’ordine è importante. In questo caso non sarebbe cambiato niente, ma con altri modificatori si possono ottenere risultati molto diversi in base all’ordine con cui vengono applicati.

var body: some View {
    VStack(alignment: .leading) {
      Text(coffee.name)
        .font(.headline)
      Text(coffee.description)
        .font(.subheadline)
        .foregroundColor(.secondary)
        .lineLimit(2)
    }
  }

Nel nostro preview iniziamo a vedere già una versione molto più accattivante di quella che potrebbe essere un elemento di una lista di caffè ma quello che manca è l’immediatezza del colpo d’occhio, è il momento giusto per inserire una bella immagine! Vogliamo la nostra immagine alla destra del titolo e della descrizione, ma i nostri componenti sono racchiusi in uno stack verticale che ci permette di impilare gli oggetti uno sopra l’altro… fortunatamente alla fine anche uno stack è una view, e può essere incapsulata in un’altra view. Inseriamo quindi lo stack verticale in uno stack orizzontale, che conterrà la nostra immagine e lo stack verticale con nome e descrizione:

HStack {
      VStack(alignment: .leading) {
        Text(coffee.name)
          .font(.headline)
        Text(coffee.description)
          .font(.subheadline)
          .foregroundColor(.secondary)
          .lineLimit(2)
      }
   }

Prima del VStack aggiungiamo un immagine:

HStack {
      Image(coffee.photo)
        .resizable()
        .frame(width: 60, height: 60)
      …

E’ importante notare che abbiamo dovuto aggiungere il modificatore .resizable(), altrimenti la foto avrebbe occupato tutto lo spazio necessario per le sue dimensioni e solo dopo abbiamo potuto specificare le esatte dimensioni che occuperà. 

Il risultato è già ottimo e in pochissime righe di codice, ma non sarebbe più professionale se le foto avessero una maschera circolare come in tutte le migliori applicazioni? Niente di più semplice, ogni View ha un modificatore per “clippare”, ovvero ritagliare l’oggetto in base ad una maschera lungo i bordi, aggiungiamo quindi:

Image(coffee.photo)
       .resizable()
       .frame(width: 60, height: 60)
       .clipShape(Circle())

Finita! Il nostro elemento di lista è pronto per essere utilizzato!

import SwiftUI

struct CoffeeRow: View {
  var coffee: Coffee

  var body: some View {
    HStack {
      Image(coffee.photo)
        .resizable()
        .frame(width: 60, height: 60)
        .clipShape(Circle())

      VStack(alignment: .leading) {
        Text(coffee.name)
          .font(.headline)
        Text(coffee.description)
          .font(.subheadline)
          .foregroundColor(.secondary)
          .lineLimit(2)
      }
    }
  }
}

struct CoffeeRow_Previews: PreviewProvider {
  static var previews: some View {
    CoffeeRow(coffee: Coffee.coffeeExample)
  }
}

Aprite adesso di nuovo il file ContentView.swift, dove di nuovo il template aveva generato un meraviglioso Hello World, questo è il file che viene mostrato all’apertura dell’App, e qui è dove andremo ad inserire la nostra lista. Prima di visualizzare una lista è necessario avere degli oggetti da mostrare, occorre quindi caricare l’elenco dei caffè presenti nel file json che avevo preparato. Aggiungete prima del body 

let menu = Bundle.main.decode([CoffeeType].self, from: "coffees.json")

Per semplicemente caricare il nostro modello dati con il contenuto del file json, questa volta non essendo una variabile ma una costante il compilatore non avrà di che lamentarsi e il preview continuerà a funzionare come prima. Adesso sostituite la text View con questo:

List {
         ForEach(menu) { menuItem in
           Section(header: Text(menuItem.name).font(.title)) {
             ForEach(menuItem.coffees) { coffee in
               CoffeeRow(coffee: coffee)
             }
           }
         }
       }

Come per magia nel preview apparirà una lista, divisa in sezioni, con ciascuna sezione riempita con i caffè che contiene. Vediamo come questo sia possibile: abbiamo aggiunto una List, l’equivalente di una UITableView, al suo interno abbiamo scorso tutti gli elementi del menu con il costruttore ForEach. Se vi ricordate il nostro modello aveva un certo numero di tipi di caffè e ciascun tipo conteneva i suoi caffè. Abbiamo chiamato ciascun tipo menuItem e lo abbiamo usato per costruire la sezione corrispondente. 

La view Section ha come parametri opzionali header e footer, noi abbiamo deciso di utilizzare l’header, che come al solito si aspetta una View (composizione, composizione, composizione): per semplicità abbiamo passato una Text con il nome del tipo di caffè modificandone la font perché sia più grande. Nella closure della sezione è necessario passare gli elementi che la compongono, di nuovo è necessario scorrere tutti gli elementi della sezione corrente e ritornare la view da visualizzare, in questo caso proprio l’oggetto CoffeeRow che abbiamo creato prima.

Sebbene sia già abbastanza manca ancora qualcosa, la barra del titolo! In UIKit questo si può ottenere utilizzando un NavigationController, in SwiftUI possiamo dire che il suo equivalente è la NavigationView. Racchiudete la List all’interno di una NavigationView in questo modo

NavigationView {
      List {
         ForEach(menu) { menuItem in
           Section(header: Text(menuItem.name).font(.title)) {
             ForEach(menuItem.coffees) { coffee in
               CoffeeRow(coffee: coffee)
             }
           }
         }
      }
    }

e nel preview comparirà immediatamente una Navigation Bar…. vuota! Ovviamente è perché non abbiamo dato nessun nome alla nostra view, qui le cose diventano un pochino più confusionarie. Per quello che abbiamo visto fino ad ora ci si aspetterebbe di aggiungere un modificatore alla NavigationView, impostando i parametri che vogliamo (titolo, pulsanti, etc…). Ricordate però che una NavigationView è l’equivalente di un NavigationController, ogni successiva view che verrà aggiunta dovrà avere il suo titolo e i suoi pulsanti. Aggiungere dei modificatori direttamente alla NavigationView significherebbe applicarli a tutte le successive view che conterrà durante il  suo life cycle, per questo è necessario applicare i modificatori direttamente agli elementi che contiene.

Quindi direttamente alla List aggiungete

List {
        ForEach(menu) { menuItem in
           Section(header: Text(menuItem.name).font(.title)) {
                     ForEach(menuItem.coffees) { coffee in
                            CoffeeRow(coffee: coffee)
                     }
           }
        }
     }
     .listStyle(GroupedListStyle())
     .navigationBarTitle(Text("Choose your fuel"))

e anche il titolo di questa View verrà correttamente visualizzato. Già che c’eravamo ho aggiunto lo stile Grouped per un effetto visivo migliore.

E’ giunto il momento di verificare il lavoro fatto (il Preview in tempo reale è bellissimo, ma vedere girare l’App è tutta un’altra cosa). Selezionate un simulatore dalla lista e lanciate l’App. Adesso abbiamo una meravigliosa lista di caffè, realizzata in pochissimo tempo, che però è completamente inutile!  ?

Navigare verso un dettaglio migliore

Non sarebbe meglio se una volta scelto il nostro caffè, potessimo vedere nel dettaglio la sua descrizione e magari avere un lista degli ingredienti principali? Iniziamo quindi a disegnare la nostra View che ospiterà il dettaglio del caffè: per prima cosa mostreremo la foto più grande, poi il nome, la descrizione e l’elenco degli ingredienti.

Create un nuovo file SwiftUI e chiamatelo CoffeeDetail. Per visualizzare i dati di un caffè occorre averlo, create quindi una variabile chiamata coffee che verrà popolata a runtime con quello selezionato dalla lista.

import SwiftUI
struct CoffeeDetail: View {
    var coffee: Coffee
    var body: some View {
        Text("Hello, World!")
    }
}

struct CoffeeDetail_Previews: PreviewProvider {
    static var previews: some View {
        CoffeeDetail(coffee: Coffee.coffeeExample)
    }
}

Abbiamo detto di voler visualizzare prima la foto e sotto il resto delle informazioni. Avremo bisogno quindi di un Vertical Stack con all’interno la nostra foto. Sostituite a Text(“Hello World”) :

VStack {
   Image(coffee.photo)
      .resizable()
      .clipShape(Circle())
}

Nel preview comparirà l’immagine centrata, ritagliata fino ad occupare tutto lo spazio in orizzontale del device. Forse un pochino troppo, provate ad inserire in po di padding orizzontale in questo modo:

VStack {
    ….
    ….
}
.padding(.horizontal)

meglio… ma c’è qualcosa che ancora non va… il caffè non è molto invitante, forse perché l’immagine ha un aspect ratio sbagliato. Aggiungete sotto il modificatore resizable() .aspectRatio(contentMode: .fit) e immediatamente il nostro caffè diventerà molto più invitante.

var body: some View {
        VStack {
            Image(coffee.photo)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .clipShape(Circle())
        }
        .padding(.horizontal)
    }

Dovremmo però cercare di far risaltare un po’ di più la nostra immagine, magari aggiungendo un bordo e un po di ombreggiatura… niente di più semplice in SwiftUI! Magari potrebbe anche essere utile in futuro riutilizzare questo tipo di immagine da altre parti nell’App, quindi invece di modificare direttamente la nostra view ne creeremo una specializzata nel visualizzare le immagini.

Create un nuovo file SwiftUI e chiamatelo RoundImage, aggiungente una variabile per contenere il nome dell’immagine che dovrà essere visualizzato e aggiungente un componente Image che la carichi.

import SwiftUI

struct RoundImage: View {
    var imageName: String
    var body: some View {
        Image(imageName)
    }
}

struct RoundImage_Previews: PreviewProvider {
    static var previews: some View {
        RoundImage(imageName: "Americano")
    }
}

Adesso aggiungiamo il clip circolare, il resize e l’aspectRatio come prima. Per ottenere un effetto migliore possiamo aggiungere un bordo, in SwiftUI semplicemente aggiungendo un overlay alla nostra immagine definendone lo shape e lo stroke:

.overlay(Circle().stroke(Color.gray, lineWidth: 4))

E per dare l’effetto “pop” magari aggiungiamo un’ombra:

.shadow(radius: 10)

Perché si veda l’ombra cambiate il colore dello stroke in bianco:

var body: some View {
        Image(imageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .clipShape(Circle())
            .overlay(
                Circle().stroke(Color.white, lineWidth: 4))
            .shadow(radius: 10)
    }

Sostituiamo adesso all’immagine di CoffeeDetail la nostra nuova RoundImage in questo modo:

var body: some View {
        VStack {
            RoundImage(imageName: coffee.photo)
        }
        .padding(.horizontal)
    }

e otterremo immediatamente un effetto più cool per la nostra View. Adesso non resta che aggiungere le altre informazioni necessarie, come la descrizione, l’elenco degli ingredienti e ovviamente il nome.

Per poter visualizzare il nome del caffè in stile iOS conviene metterlo nel titolo della Navigation Bar, che come detto in precedenza è un modificatore della View stessa. Aggiungente quindi ai modificatori del VStack :

.navigationBarTitle(coffee.name)

Per vedere il risultato direttamente nel preview dovete ricordarvi di incapsulare la view renderizzata per il preview in un proprio NavigationView, questo perché è un elemento statico slegato dal flusso dell’App, ovviamente nel lifecycle normale dell’App questa sarà all’interno del navigation view che avete impostato nella Lista.

struct CoffeeDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            CoffeeDetail(coffee: Coffee.coffeeExample)
        }
    }
}

Sotto l’immagine possiamo andare ad inserire la descrizione ed un divisorio per dare più aria alla sezione che conterrà gli ingredienti:

var body: some View {
        VStack(alignment: .leading) {
            RoundImage(imageName: coffee.photo)
            Text(coffee.description)
                .font(.body)            
            Divider() 
        }
        .padding(.all)
        .navigationBarTitle(coffee.name)
    }

Modificate anche l’allineamento dello Stack in .leading, in questo modo gli oggetti non saranno centrati nella view ma allineati a sinistra.

Gli ingredienti sono costituiti nel nostro modello dati da un array di stringhe, per migliorare un po la loro visualizzazione potremmo trasformarli in un elenco puntato, creiamoci un componente apposito per questo. Create un nuovo file SwiftUI e chiamatelo IngredientsList, aggiungete una variabile ingredients che conterrà il nostro array di stringhe come di seguito:

struct IngredientsList: View {
    var ingredients: [String]    
    var body: some View {
            }
}

Per visualizzare un elenco occorre uno Stack verticale, un identificatore e il testo… iniziamo inserendo al posto del placeholder Text un VStack, specificandone l’allineamento .leading, all’interno del quale dobbiamo renderizzare una riga per ciascun ingrediente. Come per la lista dei caffè possiamo utilizzare il ForEach per scorrere tutti gli elementi dell’array di ingredienti, l’unica differenza è che stavolta occorre specificare un identificativo univoco per ciascun elemento. Mentre i nostri oggetti CoffeeType implementano il protocollo Identifiable ciò non è direttamente vero per le stringhe, quindi è necessario comunicare a SwiftUI che ciascuna stringa è univoca:

struct IngredientsList: View {
    var ingredients: [String]    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(ingredients, id: .self) { ingredient in
                ….
            }
        }
    }
}

Utilizzando il parametro id: e assegnandoli .self abbiamo risolto il nostro problema.

Ogni riga avrà poi un immagine e un testo, occorre quindi un HStack per allinearli correttamente:

struct IngredientsList: View {
    var ingredients: [String]    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(ingredients, id: .self) { ingredient in
                HStack {
                    Image(systemName: "largecircle.fill.circle")
                    Text(ingredient)
                        .fontWeight(.semibold)
                }
                .font(.subheadline)
            }
        }
    }
}

Per l’immagine abbiamo usato una di quelle fornite all’interno dei simboli di sistema, che hanno il vantaggio di essere trattate come vere e proprie font, per questo abbiamo potuto applicare il modificatore .font direttamente a tutto l’HStack, di fatto applicandolo a tutti gli elementi che contiene.

Terminato il nostro componente possiamo utilizzarlo nel dettaglio, aprite di nuovo il file CoffeeDetail e aggiungete:

struct CoffeeDetail: View {
    var coffee: Coffee
    var body: some View {
        VStack(alignment: .leading) {
            RoundImage(imageName: coffee.photo)            
            Text(coffee.description)
                .font(.body)               
            Divider()            
            Text("Ingredients")
                .font(.headline)            
            IngredientsList(ingredients: coffee.ingredients)            
        }
        .padding(.all)
        .navigationBarTitle(coffee.name)
    }
}

Manca solamente un piccolo dettaglio per completare la View, non tutti i dispositivi hanno la stesse dimensioni, sopratutto in altezza. Contenuti come questi possono variare di molto anche in base alla mole dei dati che visualizzano, è necessario quindi che vi sia la possibilità di scrollare la view quando i dati non riescono ad essere rappresentati per intero. Notoriamente utilizzare una ScrollView su UIKit è sempre stato un incubo, fortunatamente in SwiftUI è una manna dal cielo, l’unica cosa da fare è racchiudere il VStack in una ScrollView, senza preoccuparsi di altro.

struct CoffeeDetail: View {
    var coffee: Coffee    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                RoundImage(imageName: coffee.photo)                
                Text(coffee.description)
                    .font(.body)              

                Divider()                

                Text("Ingredients")
                    .font(.headline)                

                IngredientsList(ingredients: coffee.ingredients)              
            }
            .padding(.all)
            .navigationBarTitle(coffee.name)
        }
    }
}

Adesso non manca che visualizzare il dettaglio corretto quando viene selezionato un elemento dalla lista, il NavigationLink è l’elemento preposto per questo scopo, a noi occorre un NavigationLink specifico per ciascun elemento della lista. In ContentRow sostituite la riga contente CoffeeRow con:

NavigationLink(destination: CoffeeDetail(coffee: coffee)) {
                                CoffeeRow(coffee: coffee)
                            }

Immediatamente nel preview comparirà il classico disclosure indicator per ciascuna cella lista.

Provata a far partire l’App sul simulatore e adesso potete navigare avanti e indietro tra tutti i caffè della lista.

In breve tempo siamo riusciti a lavorare allo sviluppo App, seppur semplice, ma che ha la struttura classica della maggior parte delle App per iOS. Spero di avervi fatto percepire le enormi potenzialità di SwiftUI, che pur essendo agli inizi già è in grado di velocizzare notevolmente la creazione delle interfacce. Ancora non è possibile utilizzare SwiftUI per lo sviluppo app di qualsiasi tipo di applicazione, ci sono ancora molti casi in cui è più semplice utilizzare UIKit per lo stesso task, ma già dalla versione 2.0 presentata quest’anno al WWDC sono stati introdotte notevoli migliorie e nuovi componenti, indicando che la strada tracciata è la stessa intrapresa diversi anni fa con Swift.

Ovviamente ci sono ancora moltissimi aspetti di SwiftUI che andrebbero affrontati, ma questa voleva semplice essere una introduzione per eventualmente stimolare la vostra curiosità ad andare più a fondo, perché se non sarà tra un anno o due, la sensazione è che “questa è la via”. 😉

Noi in Oimmei siamo stati abbastanza “folli” da esserci occupati dello sviluppo app in SwiftUI per una delle ultime applicazioni che ci hanno richiesto, Kil0, sinceramente non è stato semplice, ma abbiamo imparata veramente tanto. Se me la sentirei di consigliare adesso lo sviluppo App in SwiftUI per un progetto completo e “complesso” come Kil0? Sinceramente io fossi in voi aspetterei di poter supportare da iOS 14 in su… ma iniziate subito a prendere familiarità con la tecnologia, create un widget, utilizzate SwiftUI per alcune view o parti della vostra app in UIKit, e sicuramente ne sarete ripagati.

Simone Figliè