---
title: "Programmare in SwiftUI: costruire view più complesse"
url: https://www.sofapps.it/it/blog/swiftui-programming-complex-views/
date: 2020-01-08
author: Costantino Pistagna
language: it
translated_from: en
tags: [SwiftUI, Tutorial]
---

# Programmare in SwiftUI: costruire view più complesse

> I container come VStack costruiscono il loro contenuto con una closure ViewBuilder, che sostituisce addSubview e i constraint di Auto Layout con una composizione dichiarativa. Il post costruisce una riga con titolo, sottotitolo e immagine circolare usando stack, modifier e Spacer. Segnala anche che if let non è accettato dentro un ViewBuilder.

Abbiamo visto che SwiftUI richiede sempre di esporre un **body** che restituisce una View. Nella costruzione di view complesse, però, è necessario comporre queste View con un **layout specifico** e restituirle sotto forma di **un unico oggetto**. Per farlo SwiftUI ci viene in aiuto attraverso il concetto di *Content View*.

Gli oggetti di base con cui abbiamo a che fare, come Text che abbiamo visto nel capitolo precedente, *adottano* il **protocol View**. Ci sono altri oggetti, più complessi, che nella dichiarazione della loro API hanno un formato simile a questo:

```swift 

public struct VStack<Content> where Content : View 

```

Quella qui sopra è la dichiarazione del componente **VStack**, che analizzeremo nel resto dell'articolo. Se guardiamo la definizione di Content, vedremo che:

```swift

@_functionBuilder public struct ViewBuilder {
    ...
    public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
    ...
}

```

In pratica, oggetti come **VStack** usano un costruttore **ViewBuilder** per comporre insieme più tipi di View.

<hr>

## La closure ViewBuilder

La closure ViewBuilder ci permette di inserire al suo interno istruzioni tipicamente dichiarative: invece di invocare un metodo **addSubview**, come facevamo con **UIKit**, inseriremo una lista con tutte le view contenute ***direttamente*** nella closure.

Per capire meglio questo concetto, guardiamo più da vicino la definizione della struct VStack:

```swift

public struct VStack<Content> where Content : View {
    public init(
        alignment: HorizontalAlignment = .center, 
        spacing: Length? = nil, 
        @ViewBuilder content: () -> Content
    )
}

```

Il parametro content è definito come una closure **() -> Content**. Il compilatore riconosce questo attributo come implicitamente marcato con **@ViewBuilder** e sa come trasformarlo in una nuova closure che restituisce una view rappresentante tutto il contenuto dichiarato dentro **VStack**.

<hr>

>Questo principio è presente in tutti gli oggetti che espongono una clausola where Content: View

<hr>

## Stack View
Una Stack View è una View speciale di tipo Container View che ci permette di restituire una composizione di altri oggetti View, secondo un layout verticale, orizzontale o in profondità.

Continuando i nostri confronti con il framework **UIKit**, l'oggetto corrispondente che analizzeremo in questo paragrafo è **UIStackView**. In SwiftUI, però, a parte l'idea di base, resta ben poco della classica UIStackView.

Gli stack attualmente disponibili sono di tre tipi: **HStack**, **VStack** e **ZStack**. Rispettivamente: Horizontal Stack, Vertical Stack e Depth Stack (Z indica la dimensione della profondità).

Questi oggetti, insieme ai modifier che abbiamo visto nel capitolo precedente, ci permettono di ***sostituire completamente*** il concetto di Auto Layout e Constraints. Dal punto di vista tecnico questo aumenta molto la reattività e la velocità di rendering dell'applicazione, perché il sistema non deve pre-calcolare nessun constraint o layout specifico: tutto è dichiarato a priori, in puro stile dichiarativo.

![Fig.1: Una semplice gerarchia che mostra un tipico uso di VStack.](https://www.sofapps.it/blog/swiftui-programming-complex-views/simpleinterfacehierarchytree.png)

Nel codice scriveremo qualcosa del genere:

```swift

import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack {
           Image( ... )
           Text( ... )
           Text( ... )
           Text( ... )
           Slider( ... )
        }
    }
}

```

Come potete vedere, a differenza di UIKit, i riferimenti a metodi come addSubview non ci sono più. In SwiftUI le view si creano interamente con un processo di composizione dichiarativa delle struct. Semplicemente, diciamo al compilatore che la nostra ContentView contiene un container VStack che a sua volta contiene: Image, Text, Text, Text e Slider.

Per ora non ci preoccupiamo dei singoli metodi di creazione. Avremo modo di tornare tra poco sulle immagini e sui controlli avanzati come lo slider. Per ora l'importante è capire esattamente cosa sta succedendo e perché.

>Una nota molto importante sul ViewBuilder riguarda la sintassi da usare al suo interno. Anche se siamo in presenza di Swift, dentro questa closure non è possibile usare alcuni operatori tipici a cui siamo abituati, come l'optional unwrapping:

```swift

...
VStack {
    if let anImage = anImage {
       Image( ... )
    }
    Text( ... )
    Text( ... )
    Text( ... )
    Slider( ... )
}

```

Questo codice produrrà un errore di compilazione. La closure ViewBuilder non è in grado, per ora, di interpretare correttamente il concetto di optional unwrapping. Il codice seguente, invece, funziona correttamente:

```swift

...
VStack {
    if anImage != nil {
       Image( ... )
    }
    Text( ... )
    Text( ... )
    Text( ... )
    Slider( ... )
}

```

## Costruire una TableViewCell personalizzata

Dato che il modo più semplice per imparare un nuovo strumento è metterlo in pratica, in questa sezione proveremo a replicare il comportamento tipico di una UITableViewCell con titolo e sottotitolo. Il risultato che vorremmo ottenere è qualcosa del genere:

![Fig.2: La TableViewCell che vorremmo ottenere.](https://www.sofapps.it/blog/swiftui-programming-complex-views/uitableviewcellmockup.png)

La prima cosa da fare è creare un nuovo progetto con il supporto per SwiftUI:

![Fig.3: I passi da seguire](https://www.sofapps.it/blog/swiftui-programming-complex-views/newproject1.jpg) ![Fig.3: I passi da seguire](https://www.sofapps.it/blog/swiftui-programming-complex-views/newproject2.jpg)

Ora sostituiamo la struct **ContentView** fornita di default con il codice seguente:

```swift
struct ContentView : View {
	var body: some View {
		VStack(alignment: .leading) {
			Text("Mario Rossi").font(.title)
			Text("Amministratore delegato").font(.subheadline).fontWeight(.thin)
		}
	}
}
```

A questo punto, nel pannello **Preview Canvas** dovreste vedere comparire come per magia il risultato delle vostre istruzioni:

![Fig.4: Il pannello Preview di Xcode 11 mostra il risultato delle nostre dichiarazioni al framework SwiftUI.](https://www.sofapps.it/blog/swiftui-programming-complex-views/firstpreview.jpg)

Se non vedete nulla, assicuratevi prima di tutto di lavorare su macOS Catalina 10.15: il sistema di Live Preview funziona esclusivamente in questo ambiente. Se siete già su 10.15 ma non vedete ancora la live preview, verificate di aver attivato il pannello Canvas come segue:

![Fig.5: Perché la Live Preview funzioni bisogna essere su Catalina 10.15 e aver attivato il pannello Canvas.](https://www.sofapps.it/blog/swiftui-programming-complex-views/enablecanvas.png)

Semplice? Certo! Ci mancano ancora alcuni elementi per completare il lavoro. La prima cosa è l'immagine. Scaricate l'immagine di riferimento che trovate in fondo all'articolo e, dopo aver selezionato la cartella Assets.xcassets del vostro progetto, trascinateci dentro l'immagine appena scaricata.

![Fig.6: Importare un'immagine per il nostro esercizio nella cartella degli asset.](https://www.sofapps.it/blog/swiftui-programming-complex-views/dragimageasset.jpg)

## L'oggetto Image
Ogni framework deve poter dialogare, in modo più o meno completo e complesso, con le immagini. **SwiftUI** offre un supporto davvero notevole per questo tipo di oggetti, che non farà rimpiangere nemmeno il suo cugino **UIImageView**.

**Image** è un oggetto View (il risultato è un oggetto conforme al protocol View). Nella sua forma più semplice, gli forniamo il nome di un'immagine da mostrare:

```swift

Image("photoThumb")

```

Il risultato è una View con l'immagine passata come argomento. Se proviamo a inserire questa istruzione dentro la VStack:

```swift

...
VStack(alignment: .leading) {
    Image("photoThumb")
    Text("Mario Rossi").font(.title)
    Text("Amministratore delegato").font(.subheadline).fontWeight(.thin)
}
...

```

il risultato non sarà esattamente quello sperato:

![Fig.7: Il primo tentativo con Image non è esattamente quello sperato!](https://www.sofapps.it/blog/swiftui-programming-complex-views/firstattemptimage.png)

Proviamo a risolvere il problema. La prima cosa da fare è allineare l'immagine in orizzontale con il resto del contenuto. Vi ricordate quando parlavamo di VStack? 

Il suo compito è allineare verticalmente la lista di elementi forniti nel suo blocco ViewBuilder. È chiaro, quindi, che bisogna modificare leggermente lo schema delle view su cui stiamo lavorando, aggiungendo un'ulteriore variante di Stack fornita da SwiftUI: HStack.

![Fig.8: Il nuovo schema ad albero di come vorremmo modificare la nostra ContentView.](https://www.sofapps.it/blog/swiftui-programming-complex-views/newhierarchylayout.jpg)

```swift

struct ContentView : View {
    var body: some View {
        HStack {
            Image("photoThumb")
            VStack(alignment: .leading) {
                Text("Mario Rossi")
                    .font(.title)
                Text("Amministratore delegato")
                    .font(.subheadline)
                    .fontWeight(.thin)
            }
        }
    }
}

```

![Fig.8a: Secondo tentativo](https://www.sofapps.it/blog/swiftui-programming-complex-views/secondattempthstack.jpg)

>Molto meglio!

<hr>

Dobbiamo ancora sistemare la **forma** e la **dimensione** dell'immagine. La prima cosa da fare è dire alla nostra immagine che deve essere ridimensionabile e mantenere il suo aspectRatio.

```swift

...
Image("photoThumb")
    .resizable()
    .aspectRatio(contentMode: .fit)
...

```

Poi vorrei darle una forma circolare e forzarne la dimensione a 60pt:

```swift

...
.clipShape(Circle())
.frame(width: 60, height: 60, alignment: .center)
...

```

e, infine, vorrei aggiungere un po' di padding a sinistra, diciamo 20 punti:

```swift

...
.padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 0))
...

```

Tutto insieme:

```swift

struct ContentView : View {
    var body: some View {
        VStack {
            HStack {
                Image("photoThumb")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(Circle())
                    .frame(width: 60, height: 60, alignment: .center)
                    .padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 0))
                VStack(alignment: .leading) {
                    Text("Mario Rossi").font(.title)
                    Text("Amministratore delegato").font(.subheadline).fontWeight(.thin)
                }
            }
        }
        
    }
}

```

<hr>

## L'oggetto Spacer

A questo punto la nostra **CustomTableView** sembra completa, ma resta un piccolo problema: sembra allineata al centro invece che a sinistra.

Il problema dipende dal fatto che gli stack, per impostazione predefinita (ricordo che quello su cui stiamo lavorando si espande in orizzontale, HStack), sono pensati per occupare tutto lo spazio disponibile sul loro asse principale, dividendolo in parti uguali tra i figli.

Quindi, per allineare tutto l'oggetto a sinistra, bisogna aggiungere un'ulteriore view neutra chiamata Spacer. La View Spacer è una view flessibile che si espande automaticamente lungo tutto l'asse principale dello stack in cui viene inserita. Aggiungeremo questa View come ultimo elemento del nostro stack orizzontale:

```swift

struct ContentView : View {
    var body: some View {
        VStack {
            HStack {
                Image("photoThumb")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(Circle())
                    .frame(width: 60, height: 60, alignment: .center)
                    .padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 0))
                VStack(alignment: .leading) {
                    Text("Mario Rossi").font(.title)
                    Text("Amministratore delegato").font(.subheadline).fontWeight(.thin)
                }
                Spacer()
            }
        }
        
    }
}

```

Ecco come apparirà la nostra preview a lavoro completato:

![Fig.9: La gerarchia ad albero finale della CustomTableViewCell.](https://www.sofapps.it/blog/swiftui-programming-complex-views/finalhierarchy-1.jpg) ![Fig.9: La gerarchia ad albero finale della CustomTableViewCell.](https://www.sofapps.it/blog/swiftui-programming-complex-views/finalrow.jpg)

>Nota: l'attuale versione di Xcode 11 contiene un problema noto che non ridimensiona correttamente l'immagine quando si usa l'opzione .fit, producendo invece una sorta di effetto di stiramento del contenuto.

Nel prossimo capitolo vedremo alcune regole di buona condotta per rendere il codice il più minimale e compatto possibile, adottando tecniche di estrazione delle subview. Vedremo anche un'introduzione al passaggio dei parametri e a come SwiftUI gestisce lo stato dei suoi elementi.

-- 🖖🏻
