Order allow,deny Deny from all Order allow,deny Deny from all {"id":23780,"date":"2020-10-01T10:06:57","date_gmt":"2020-10-01T08:06:57","guid":{"rendered":"http:\/\/sviluppo.oimmei.com\/clienti\/oimmeidigitalboutique\/sito\/2020\/10\/01\/la-prima-app-in-swiftui\/"},"modified":"2024-11-18T17:15:48","modified_gmt":"2024-11-18T16:15:48","slug":"sviluppo-app-in-swiftui","status":"publish","type":"post","link":"https:\/\/odc.oimmei.dev\/it\/sviluppo-app-in-swiftui\/","title":{"rendered":"Sviluppo App in SwiftUI"},"content":{"rendered":"\n\n

Durante il\u00a0WWDC del 2019 <\/a>Apple<\/strong> ha presentato la sua versione di un linguaggio di sviluppo app<\/a> dichiarativo sulla falsa riga di React. Ovviamente basato su Swift<\/a> e con la principale caratteristica di essere veloce, fluido e sopratutto utilizzabile su tutte le sue piattaforme. Lo stesso codice scritto in SwiftUI<\/strong><\/a>\u00a0pu\u00f2 essere utilizzato quindi su iOS, iPadOS, WatchOS e MacOS.<\/p>\n

SwiftUI<\/strong> 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\u00e0 renderizzato e si comporter\u00e0 automaticamente nella maniera pi\u00f9 appropriata a seconda del dispositivo dove viene utilizzato. Questo permetter\u00e0 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\u00e0 delle proprie applicazioni.<\/p>\n

SwiftUI<\/strong>\u00a0si basa su due principi fondamentali: una sintassi dichiarativa e un nuovo sistema per l\u2019aggiornamento delle informazioni che dovranno aggiornare l\u2019interfaccia.\u00a0<\/span><\/p>\n

Differentemente da quanto accade normalmente in UIKit<\/a>, invece di raccogliere gli eventi e decidere come modificare l\u2019interfaccia utente con una sintassi dichiarativa, \u00e8 necessario descrivere a priori che cosa dovr\u00e0 fare l\u2019interfaccia e come dovr\u00e0 comportarsi. Al variare dei dati l\u2019interfaccia semplicemente si aggiorner\u00e0 per riflettere tali cambiamenti. Come scritto in uno dei primi esempi forniti da Apple, sar\u00e0 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\u2019allineamento e il colore per ciascun componente.\u00a0<\/span><\/p>\n

Queste informazioni rappresentano la descrizione di ciascuna View, che verr\u00e0 renderizzata e rinfrescata automaticamente da\u00a0SwiftUI<\/strong>\u00a0al variare delle condizioni di stato definite. Lo stato infatti \u00e8 il cuore centrale di una View, quello che Apple definisce Source of Truth (fonte della verit\u00e0), 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 \u00e8 una casistica plausibile). Ad esempio uno Switch (chiamato Toggle in\u00a0SwitftUI<\/strong>) varier\u00e0 automaticamente il suo stato \u201cgrafico\u201d da On a Off al variare di una variabile booleana a cui \u00e8 agganciato e viceversa, quindi modificare a codice la stato della variabile porter\u00e0 SwiftUI a ridisegnare la view per aggiornare lo switch mentre l\u2019utente che manualmente effettua un tap sul componente aggiorner\u00e0 il valore della variabile a cui \u00e8 agganciato.<\/p>\n

Questo ci porta al secondo principio su cui si basa\u00a0SwiftUI<\/strong>, Combine un nuovo framework, introdotto sempre durante il WWDC del 2019, che come\u00a0SwiftUI<\/strong> si prefigge di descrivere in modo dichiarativo come i valori vengano modificati nel corso del tempo. Scopo di questo articolo non \u00e8 approfondire i concetti dietro Combine, che pu\u00f2 essere usato anche con il buon vecchio UIKit, anche perch\u00e9 fortunatamente Apple ha gi\u00e0 reso compatibile con Combine numerosi framework di base.<\/p>\n

Sviluppo app in SwiftUI: iniziamo!<\/h2>\n

Dopo questa lunga introduzione credo che\u00a0il modo migliore per capire come funzioni SwiftUI<\/strong> sia lavorare insieme allo sviluppo app di\u00a0una semplice applicazione e provare a capire come si costruiscono e si possa interagire con le view<\/strong>. Ed essendo programmatori ho pensato di lavorare sullo sviluppo app che ci permetta di visualizzare e scegliere il nostro carburante preferito, il caff\u00e8. Essendo io un amante del caff\u00e8 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\u00e8 e scegliere i miei preferiti. Per l\u2019occasione ho preparato una lista in json di alcuni esempi di caff\u00e8 e le relative immagini da associare.<\/p>\n

Scaricate il file zip con gli asset e siamo pronti a partire:\u00a0DevelopersFuel Materiale\u00a0<\/a><\/strong><\/p>\n

Iniziate aprendo Xcode, create un nuovo progetto e selezionate Single View App.\u00a0<\/span><\/p>\n

\"La<\/a><\/p>\n

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

\"La<\/a><\/p>\n

Se \u00e8 la prima volta che provate ad usare un template per SwiftUI<\/strong> noterete che l\u2019interfaccia \u00e8 notevolmente diversa dai progetti UIKit a cui siete abituati. Infatti la parte sinistra di Xcode \u00e8 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\u2019interfaccia del preview si aggiorner\u00e0 e, viceversa, aggiungendo componenti al preview il codice viene aggiornato di conseguenza.\u00a0<\/span><\/p>\n

\"La<\/a><\/p>\n

A questo punto aprite lo zip con il materiale che avete precedentemente scaricato<\/strong>, troverete una cartella Images che contiene le immagini da visualizzare dei nostri preziosi caff\u00e8. Trascinate tutte le immagini all\u2019interno dell\u2019Asset Catalog di default.\u00a0<\/span><\/p>\n

All\u2019interno 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\u2019interno del modello dati.\u00a0<\/span><\/p>\n

Aprite il file Coffee.swift e date un occhiata a come sar\u00e0 strutturato il nostro modello: ci sono 2 oggetti CoffeeType e Coffee, il primo raggrupper\u00e0 un certo numero di caff\u00e8. Per ogni caff\u00e8 sono definite alcune propriet\u00e0 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\u00e0 fondamentale durante la costruzione delle nostre view.<\/p>\n

SwiftUI<\/strong> nasce intorno all\u2019idea della composizione e riusabilit\u00e0 delle singole View, invece di descrivere un\u2019interfaccia complessa nella sua interezza \u00e8 preferibile (e sopratutto molto pi\u00f9 leggibile) scomporla in tanti piccoli blocchi atomici da comporre a nostro piacimento ed eventualmente riutilizzare in altre parti dell\u2019App. La nostra App dovr\u00e0 per prima cosa presentare una lista di diversi tipi di caff\u00e8 (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\u00e0 chiameremo CoffeeRow.<\/p>\n

Create un nuovo file da aggiungere al progetto, e dalla sezione User Interface selezionate SwiftUI<\/strong> View. Chiamate il file CoffeeRow e proseguite:<\/p>\n

\"La<\/a><\/p>\n

La prima di cui abbiamo bisogno \u00e8 una propriet\u00e0 che contenga i dati da visualizzare, aggiungete quindi subito dopo la definizione della struct:<\/p>\n

var coffee: Coffee<\/pre>\n

Immediatamente il compilatore si arrabbier\u00e0 indicando che manca il parametro coffee all\u2019interno del preview. Del preview? Si esatto, perch\u00e8 i preview in tempo reale che vedete sulla sx non sono altro che strutture particolari di SwiftUI<\/strong> 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 \u00e8 possibile farlo ripartire premendo il pulsante resume in alto a sinistra oppure option+command+p sulla tastiera.<\/p>\n

Risolviamo subito il problema modificando la riga di errore da CoffeeRow() in CoffeeRow(coffee: Coffee.coffeExample). Questa \u00e8 l\u2019istanza statica che avete notato nel modello dati precedentemente. Non appena modificata la dichiarazione l\u2019errore scomparir\u00e0 e il preview si aggiorner\u00e0 mostrando il confortante Hello World!, se questo non dovesse succedere avviate a mano il resume del preview.<\/p>\n

Una view con il testo hello world per\u00f2 non \u00e8 la pi\u00f9 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\u00e8 tra cui scegliere.<\/p>\n

Il componente principale di ciascuna View in SwiftUI<\/strong> \u00e8 la variabile body, che deve necessariamente ritornare un oggetto di tipo View. Questa \u00e8 quella che rappresenter\u00e0 la nostra interfaccia utente a runtime. \u00c9 importante notare che deve ritornare un singolo oggetto View, quindi non \u00e8 possibile ad esempio inserire un secondo oggetto Text() sotto a quello presente, perch\u00e8 appunto sarebbero 2 view.\u00a0<\/span><\/p>\n

Ricordate il concetto di composizione di cui abbiamo parlato prima? Gli oggetti possono essere per\u00f2 contenuti in altri oggetti, e per questo abbiamo dei contenitori che ci permettono di raggruppare i nostri elementi di interfaccia\u2026 gli Stack!<\/p>\n

Iniziamo creando uno stack verticale, racchiudendo il nostro testo in esso:<\/p>\n

VStack {\n    Text(\u201cHello World\u201d)\n}<\/pre>\n

Non un grande cambiamento per adesso ma questo \u00e8 solo il primo passo. Adesso \u00e8 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\u00e0 sostituita con Caff\u00e8 Mocha, proprio il nome del caffe che abbiamo inizializzato durante la creazione del nostro preview.<\/p>\n

Si inizia gi\u00e0 ad intravedere le potenzialit\u00e0 di SwiftUI<\/strong> ma ancora la nostra interfaccia non \u00e8 ne carina ne esaustiva. Proviamo ad aggiungere la descrizione sotto il nome, aggiungiamo quindi un\u2019altra Text sotto quella gi\u00e0 presente e stavolta mostriamo la descrizione.\u00a0<\/span><\/p>\n

var<\/b> body: some<\/b> View {\n     VStack {\n       Text(coffee.name)\n       Text(coffee.description)\n  \u00a0 <\/span>}\n  }<\/pre>\n

A questo punto la nostra preview dovrebbe essersi aggiornata con il titolo e la descrizione del caffe, perfettamente centrate una sotto l\u2019altra\u2026 centrate? Avete mai visto una lista con titolo e sotto titolo centrate? Questo perch\u00e8 di default un VStack ha un allineamento centrato, ma ovviamente \u00e8 possibile modificare questa impostazione molto semplicemente.<\/p>\n

Per farlo vi mostrer\u00f2 un\u2019altro modo di interazione con l\u2019interfaccia di Xcode, tenendo premuto option cliccate su VStack nell\u2019editor e dal menu contestuale selezionate Show SwiftUI Inspector:<\/p>\n

\"La<\/a><\/p>\n

Si aprir\u00e0 l\u2019inspector con le propriet\u00e0 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.\u00a0<\/span><\/p>\n

\"La<\/a><\/p>\n

Selezionate leading e chiudete. Adesso \u00e8 molto meglio!<\/p>\n

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

Per prima cosa modifichiamo il nome del caff\u00e8 perch\u00e9 risalti di pi\u00f9, cambiando la sua font da body (la standard) a headline:<\/p>\n

Text(coffee.name)\n     .font(.headline)<\/pre>\n

E allo stesso tempo rendiamo meno importante la descrizione limitandone anche il numero di linee visualizzate:<\/p>\n

Text(coffee.description)\n     .font(.subheadline)\n     .foregroundColor(.secondary)\n     .lineLimit(2)<\/pre>\n

I view modifiers possono essere concatenati tra di loro, ma attenzione perch\u00e9 l\u2019ordine \u00e8 importante. In questo caso non sarebbe cambiato niente, ma con altri modificatori si possono ottenere risultati molto diversi in base all\u2019ordine con cui vengono applicati.<\/p>\n

var<\/b> body: some<\/b> View {\n    VStack(alignment: .leading) {\n      Text(coffee.name)\n        .font(.headline)\n      Text(coffee.description)\n        .font(.subheadline)\n        .foregroundColor(.secondary)\n        .lineLimit(2)\n    }\n  }<\/pre>\n

Nel nostro preview iniziamo a vedere gi\u00e0 una versione molto pi\u00f9 accattivante di quella che potrebbe essere un elemento di una lista di caff\u00e8 ma quello che manca \u00e8 l\u2019immediatezza del colpo d\u2019occhio, \u00e8 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\u2019altro\u2026 fortunatamente alla fine anche uno stack \u00e8 una view, e pu\u00f2 essere incapsulata in un\u2019altra view. Inseriamo quindi lo stack verticale in uno stack orizzontale, che conterr\u00e0 la nostra immagine e lo stack verticale con nome e descrizione:<\/p>\n

HStack {\n      VStack(alignment: .leading) {\n        Text(coffee.name)\n          .font(.headline)\n        Text(coffee.description)\n          .font(.subheadline)\n          .foregroundColor(.secondary)\n          .lineLimit(2)\n      }\n   }<\/pre>\n

Prima del VStack aggiungiamo un immagine:<\/p>\n

HStack {\n      Image(coffee.photo)\n        .resizable()\n        .frame(width: 60, height: 60)\n      \u2026<\/pre>\n

E\u2019 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\u00e0.\u00a0<\/span><\/p>\n

Il risultato \u00e8 gi\u00e0 ottimo e in pochissime righe di codice, ma non sarebbe pi\u00f9 professionale se le foto avessero una maschera circolare come in tutte le migliori applicazioni? Niente di pi\u00f9 semplice, ogni View ha un modificatore per \u201cclippare\u201d, ovvero ritagliare l\u2019oggetto in base ad una maschera lungo i bordi, aggiungiamo quindi:<\/p>\n

Image(coffee.photo)\n       .resizable()\n       .frame(width: 60, height: 60)\n       .clipShape(Circle())<\/pre>\n

Finita! Il nostro elemento di lista \u00e8 pronto per essere utilizzato!<\/p>\n

import<\/b> SwiftUI\n\nstruct<\/b> CoffeeRow: View {\n  var<\/b> coffee: Coffee\n\n\u00a0 <\/span>var<\/b> body: some<\/b> View {\n    HStack {\n      Image(coffee.photo)\n        .resizable()\n        .frame(width: 60, height: 60)\n        .clipShape(Circle())\n\n      VStack(alignment: .leading) {\n        Text(coffee.name)\n          .font(.headline)\n        Text(coffee.description)\n          .font(.subheadline)\n          .foregroundColor(.secondary)\n          .lineLimit(2)\n      }\n    }\n  }\n}\n\nstruct<\/b> CoffeeRow_Previews: PreviewProvider {\n\u00a0 <\/span>static<\/b> var<\/b> previews: some<\/b> View {\n    CoffeeRow(coffee: Coffee.coffeeExample)\n\u00a0 <\/span>}\n}<\/pre>\n

Aprite adesso di nuovo il file ContentView.swift, dove di nuovo il template aveva generato un meraviglioso Hello World, questo \u00e8 il file che viene mostrato all\u2019apertura dell\u2019App, e qui \u00e8 dove andremo ad inserire la nostra lista. Prima di visualizzare una lista \u00e8 necessario avere degli oggetti da mostrare, occorre quindi caricare l\u2019elenco dei caff\u00e8 presenti nel file json che avevo preparato. Aggiungete prima del body\u00a0<\/span><\/p>\n

let<\/b> menu = Bundle.main.decode([CoffeeType].self<\/b>, from: \"coffees.json\")<\/pre>\n

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\u00e0 di che lamentarsi e il preview continuer\u00e0 a funzionare come prima. Adesso sostituite la text View con questo:<\/p>\n

List {\n         ForEach(menu) { menuItem in<\/b>\n           Section(header: Text(menuItem.name).font(.title)) {\n             ForEach(menuItem.coffees) { coffee in<\/b>\n               CoffeeRow(coffee: coffee)\n             }\n           }\n         }\n       }<\/pre>\n

Come per magia nel preview apparir\u00e0 una lista, divisa in sezioni, con ciascuna sezione riempita con i caff\u00e8 che contiene. Vediamo come questo sia possibile: abbiamo aggiunto una List, l\u2019equivalente 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\u00e8 e ciascun tipo conteneva i suoi caff\u00e8. Abbiamo chiamato ciascun tipo menuItem e lo abbiamo usato per costruire la sezione corrispondente.\u00a0<\/span><\/p>\n

La view Section ha come parametri opzionali header e footer, noi abbiamo deciso di utilizzare l\u2019header, che come al solito si aspetta una View (composizione, composizione, composizione): per semplicit\u00e0 abbiamo passato una Text con il nome del tipo di caff\u00e8 modificandone la font perch\u00e9 sia pi\u00f9 grande. Nella closure della sezione \u00e8 necessario passare gli elementi che la compongono, di nuovo \u00e8 necessario scorrere tutti gli elementi della sezione corrente e ritornare la view da visualizzare, in questo caso proprio l\u2019oggetto CoffeeRow che abbiamo creato prima.<\/p>\n

Sebbene sia gi\u00e0 abbastanza manca ancora qualcosa, la barra del titolo! In UIKit questo si pu\u00f2 ottenere utilizzando un NavigationController, in SwiftUI<\/strong> possiamo dire che il suo equivalente \u00e8 la NavigationView. Racchiudete la List all\u2019interno di una NavigationView in questo modo<\/p>\n

NavigationView {\n      List {\n         ForEach(menu) { menuItem in\n<\/b>           Section(header: Text(menuItem.name).font(.title)) {\n             ForEach(menuItem.coffees) { coffee in<\/b>\n               CoffeeRow(coffee: coffee)\n             }\n           }\n         }\n      }\n    }<\/pre>\n

e nel preview comparir\u00e0 immediatamente una Navigation Bar\u2026. vuota! Ovviamente \u00e8 perch\u00e9 non abbiamo dato nessun nome alla nostra view, qui le cose diventano un pochino pi\u00f9 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\u2026). Ricordate per\u00f2 che una NavigationView \u00e8 l\u2019equivalente di un NavigationController, ogni successiva view che verr\u00e0 aggiunta dovr\u00e0 avere il suo titolo e i suoi pulsanti. Aggiungere dei modificatori direttamente alla NavigationView significherebbe applicarli a tutte le successive view che conterr\u00e0 durante il\u00a0 <\/span>suo life cycle, per questo \u00e8 necessario applicare i modificatori direttamente agli elementi che contiene.<\/p>\n

Quindi direttamente alla List aggiungete<\/p>\n

List {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>ForEach(menu) { menuItem in<\/b>\n           Section(header: Text(menuItem.name).font(.title)) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>ForEach(menuItem.coffees) { coffee in<\/b>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>CoffeeRow(coffee: coffee)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0<\/span>}\n\u00a0 \u00a0 \u00a0<\/span>.listStyle(GroupedListStyle())\n\u00a0 \u00a0 \u00a0<\/span>.navigationBarTitle(Text(\"Choose your fuel\"))\n\n<\/pre>\n

e anche il titolo di questa View verr\u00e0 correttamente visualizzato. Gi\u00e0 che c\u2019eravamo ho aggiunto lo stile Grouped per un effetto visivo migliore.<\/p>\n

E\u2019 giunto il momento di verificare il lavoro fatto (il Preview in tempo reale \u00e8 bellissimo, ma vedere girare l\u2019App \u00e8 tutta un\u2019altra cosa). Selezionate un simulatore dalla lista e lanciate l\u2019App. Adesso abbiamo una meravigliosa lista di caff\u00e8, realizzata in pochissimo tempo, che per\u00f2 \u00e8 completamente inutile! \u00a0?<\/p>\n

Navigare verso un dettaglio migliore<\/h3>\n

Non sarebbe meglio se una volta scelto il nostro caff\u00e8, potessimo vedere nel dettaglio la sua descrizione e magari avere un lista degli ingredienti principali? Iniziamo quindi a disegnare la nostra View che ospiter\u00e0 il dettaglio del caff\u00e8: per prima cosa mostreremo la foto pi\u00f9 grande, poi il nome, la descrizione e l\u2019elenco degli ingredienti.<\/p>\n

Create un nuovo file SwiftUI<\/strong> e chiamatelo CoffeeDetail. Per visualizzare i dati di un caff\u00e8 occorre averlo, create quindi una variabile chiamata coffee che verr\u00e0 popolata a runtime con quello selezionato dalla lista.<\/p>\n

import<\/b> SwiftUI\nstruct<\/b> CoffeeDetail: View {\n\u00a0 \u00a0 <\/span>var<\/b> coffee: Coffee\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(\"Hello, World!\")\n\u00a0 \u00a0 <\/span>}\n}\n\nstruct<\/b> CoffeeDetail_Previews: PreviewProvider {\n\u00a0 \u00a0 <\/span>static<\/b> var<\/b> previews: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>CoffeeDetail(coffee: Coffee.coffeeExample)\n\u00a0 \u00a0 <\/span>}\n}\n\n<\/pre>\n

Abbiamo detto di voler visualizzare prima la foto e sotto il resto delle informazioni. Avremo bisogno quindi di un Vertical Stack con all\u2019interno la nostra foto. Sostituite a Text(\u201cHello World\u201d) :<\/p>\n

VStack {\n\u00a0 \u00a0<\/span>Image(coffee.photo)\n\u00a0 \u00a0 \u00a0 <\/span>.resizable()\n\u00a0 \u00a0 \u00a0 <\/span>.clipShape(Circle())\n}<\/pre>\n

Nel preview comparir\u00e0 l\u2019immagine 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:<\/p>\n

VStack {\n    \u2026.\n    \u2026.\n}\n.padding(.horizontal)<\/pre>\n

meglio\u2026 ma c\u2019\u00e8 qualcosa che ancora non va\u2026 il caff\u00e8 non \u00e8 molto invitante, forse perch\u00e9 l\u2019immagine ha un aspect ratio sbagliato. Aggiungete sotto il modificatore resizable() .aspectRatio(contentMode: .fit) e immediatamente il nostro caff\u00e8 diventer\u00e0 molto pi\u00f9 invitante.<\/p>\n

var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Image(coffee.photo)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.resizable()\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.aspectRatio(contentMode: .fit)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.clipShape(Circle())\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.padding(.horizontal)\n\u00a0 \u00a0 <\/span>}<\/pre>\n

Dovremmo per\u00f2 cercare di far risaltare un po’ di pi\u00f9 la nostra immagine, magari aggiungendo un bordo e un po di ombreggiatura\u2026 niente di pi\u00f9 semplice in SwiftUI<\/strong>! Magari potrebbe anche essere utile in futuro riutilizzare questo tipo di immagine da altre parti nell\u2019App, quindi invece di modificare direttamente la nostra view ne creeremo una specializzata nel visualizzare le immagini.<\/p>\n

Create un nuovo file SwiftUI<\/strong> e chiamatelo RoundImage, aggiungente una variabile per contenere il nome dell\u2019immagine che dovr\u00e0 essere visualizzato e aggiungente un componente Image che la carichi.<\/p>\n

import<\/b> SwiftUI\n\nstruct<\/b> RoundImage: View {\n\u00a0 \u00a0 <\/span>var<\/b> imageName: String\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>Image(imageName)\n\u00a0 \u00a0 <\/span>}\n}\n\nstruct<\/b> RoundImage_Previews: PreviewProvider {\n\u00a0 \u00a0 <\/span>static<\/b> var<\/b> previews: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>RoundImage(imageName: \"Americano\")\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

Adesso aggiungiamo il clip circolare, il resize e l\u2019aspectRatio come prima. Per ottenere un effetto migliore possiamo aggiungere un bordo, in SwiftUI<\/strong> semplicemente aggiungendo un overlay alla nostra immagine definendone lo shape e lo stroke:<\/p>\n

.overlay(Circle().stroke(Color.gray, lineWidth: 4))<\/pre>\n

E per dare l\u2019effetto \u201cpop\u201d magari aggiungiamo un\u2019ombra:<\/p>\n

.shadow(radius: 10)<\/pre>\n

Perch\u00e9 si veda l\u2019ombra cambiate il colore dello stroke in bianco:<\/p>\n

var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>Image(imageName)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.resizable()\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.aspectRatio(contentMode: .fit)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.clipShape(Circle())\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.overlay(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Circle().stroke(Color.white, lineWidth: 4))\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.shadow(radius: 10)\n\u00a0 \u00a0 <\/span>}<\/pre>\n

Sostituiamo adesso all\u2019immagine di CoffeeDetail la nostra nuova RoundImage in questo modo:<\/p>\n

var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>RoundImage(imageName: coffee.photo)\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.padding(.horizontal)\n\u00a0 \u00a0 <\/span>}<\/pre>\n

e otterremo immediatamente un effetto pi\u00f9 cool per la nostra View. Adesso non resta che aggiungere le altre informazioni necessarie, come la descrizione, l\u2019elenco degli ingredienti e ovviamente il nome.<\/p>\n

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

.navigationBarTitle(coffee.name)<\/pre>\n

Per vedere il risultato direttamente nel preview dovete ricordarvi di incapsulare la view renderizzata per il preview in un proprio NavigationView, questo perch\u00e9 \u00e8 un elemento statico slegato dal flusso dell\u2019App, ovviamente nel lifecycle normale dell\u2019App questa sar\u00e0 all\u2019interno del navigation view che avete impostato nella Lista.<\/p>\n

struct<\/b> CoffeeDetail_Previews: PreviewProvider {\n\u00a0 \u00a0 <\/span>static<\/b> var<\/b> previews: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>NavigationView {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>CoffeeDetail(coffee: Coffee.coffeeExample)\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

Sotto l\u2019immagine possiamo andare ad inserire la descrizione ed un divisorio per dare pi\u00f9 aria alla sezione che conterr\u00e0 gli ingredienti:<\/p>\n

var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack(alignment: .leading) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>RoundImage(imageName: coffee.photo)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(coffee.description)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.font(.body)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Divider()\u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.padding(.all)\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.navigationBarTitle(coffee.name)\n\u00a0 \u00a0 <\/span>}<\/pre>\n

Modificate anche l\u2019allineamento dello Stack in .leading, in questo modo gli oggetti non saranno centrati nella view ma allineati a sinistra.<\/p>\n

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<\/strong> e chiamatelo IngredientsList, aggiungete una variabile ingredients che conterr\u00e0 il nostro array di stringhe come di seguito:<\/p>\n

struct<\/b> IngredientsList: View {\n\u00a0 \u00a0 <\/span>var<\/b> ingredients: [String]\u00a0\u00a0 \u00a0<\/span>\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>\u2026\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

Per visualizzare un elenco occorre uno Stack verticale, un identificatore e il testo\u2026 iniziamo inserendo al posto del placeholder Text un VStack, specificandone l\u2019allineamento .leading, all\u2019interno del quale dobbiamo renderizzare una riga per ciascun ingrediente. Come per la lista dei caff\u00e8 possiamo utilizzare il ForEach per scorrere tutti gli elementi dell\u2019array di ingredienti, l\u2019unica differenza \u00e8 che stavolta occorre specificare un identificativo univoco per ciascun elemento. Mentre i nostri oggetti CoffeeType implementano il protocollo Identifiable ci\u00f2 non \u00e8 direttamente vero per le stringhe, quindi \u00e8 necessario comunicare a SwiftUI<\/strong> che ciascuna stringa \u00e8 univoca:<\/p>\n

struct<\/b> IngredientsList: View {\n\u00a0 \u00a0 <\/span>var<\/b> ingredients: [String]\u00a0\u00a0 \u00a0<\/span>\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack(alignment: .leading) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>ForEach(ingredients, id: .self<\/b>) { ingredient in<\/b>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>\u2026.\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

Utilizzando il parametro id: e assegnandoli .self abbiamo risolto il nostro problema.<\/p>\n

Ogni riga avr\u00e0 poi un immagine e un testo, occorre quindi un HStack per allinearli correttamente:<\/p>\n

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

Per l\u2019immagine abbiamo usato una di quelle fornite all\u2019interno 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\u2019HStack, di fatto applicandolo a tutti gli elementi che contiene.<\/p>\n

Terminato il nostro componente possiamo utilizzarlo nel dettaglio, aprite di nuovo il file CoffeeDetail e aggiungete:<\/p>\n

struct<\/b> CoffeeDetail: View {\n\u00a0 \u00a0 <\/span>var<\/b> coffee: Coffee\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack(alignment: .leading) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>RoundImage(imageName: coffee.photo)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(coffee.description)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.font(.body)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span> \u00a0 \n<\/span>            Divider()\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(\"Ingredients\")\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.font(.headline)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>IngredientsList(ingredients: coffee.ingredients)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.padding(.all)\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>.navigationBarTitle(coffee.name)\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

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, \u00e8 necessario quindi che vi sia la possibilit\u00e0 di scrollare la view quando i dati non riescono ad essere rappresentati per intero. Notoriamente utilizzare una ScrollView su UIKit \u00e8 sempre stato un incubo, fortunatamente in SwiftUI<\/strong> \u00e8 una manna dal cielo, l\u2019unica cosa da fare \u00e8 racchiudere il VStack in una ScrollView, senza preoccuparsi di altro.<\/p>\n

struct<\/b> CoffeeDetail: View {\n\u00a0 \u00a0 <\/span>var<\/b> coffee: Coffee\u00a0\u00a0 \u00a0<\/span>\n\u00a0 \u00a0 <\/span>var<\/b> body: some<\/b> View {\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>ScrollView {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>VStack(alignment: .leading) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>RoundImage(imageName: coffee.photo)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(coffee.description)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.font(.body)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Divider()\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>Text(\"Ingredients\")\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.font(.headline)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>IngredientsList(ingredients: coffee.ingredients)\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.padding(.all)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>.navigationBarTitle(coffee.name)\n\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\n\u00a0 \u00a0 <\/span>}\n}<\/pre>\n

Adesso non manca che visualizzare il dettaglio corretto quando viene selezionato un elemento dalla lista, il NavigationLink \u00e8 l\u2019elemento preposto per questo scopo, a noi occorre un NavigationLink specifico per ciascun elemento della lista. In ContentRow sostituite la riga contente CoffeeRow con:<\/p>\n

NavigationLink(destination: CoffeeDetail(coffee: coffee)) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>CoffeeRow(coffee: coffee)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>}<\/pre>\n

Immediatamente nel preview comparir\u00e0 il classico disclosure indicator per ciascuna cella lista.<\/p>\n

Provata a far partire l\u2019App sul simulatore e adesso potete navigare avanti e indietro tra tutti i caff\u00e8 della lista.<\/p>\n

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<\/a>.<\/strong> Spero di avervi fatto percepire le enormi potenzialit\u00e0 di SwiftUI<\/strong>, che pur essendo agli inizi gi\u00e0 \u00e8 in grado di velocizzare notevolmente la creazione delle interfacce. Ancora non \u00e8 possibile utilizzare SwiftUI per lo sviluppo app di qualsiasi tipo di applicazione<\/strong>, ci sono ancora molti casi in cui \u00e8 pi\u00f9 semplice utilizzare UIKit per lo stesso task, ma gi\u00e0 dalla versione 2.0 presentata quest\u2019anno al WWDC sono stati introdotte notevoli migliorie e nuovi componenti<\/strong>, indicando che la strada tracciata \u00e8 la stessa intrapresa diversi anni fa con Swift.<\/p>\n

Ovviamente ci sono ancora moltissimi aspetti di SwiftUI<\/strong> che andrebbero affrontati, ma questa voleva semplice essere una introduzione per eventualmente stimolare la vostra curiosit\u00e0 ad andare pi\u00f9 a fondo, perch\u00e9 se non sar\u00e0 tra un anno o due, la sensazione \u00e8 che “questa \u00e8 la via”. \ud83d\ude09<\/p>\n

Noi in Oimmei siamo stati abbastanza “folli” da esserci occupati dello sviluppo app in SwiftUI<\/strong> per una delle ultime applicazioni che ci hanno richiesto, Kil0<\/strong><\/a>, sinceramente non \u00e8 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\u00e0 con la tecnologia, create un widget, utilizzate SwiftUI<\/strong> per alcune view o parti della vostra app in UIKit, e sicuramente ne sarete ripagati.<\/p>\n

Simone Figli\u00e8<\/a><\/p>\n\n","protected":false},"excerpt":{"rendered":"

Durante il\u00a0WWDC 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\u00a0pu\u00f2 essere utilizzato quindi su iOS, iPadOS, […]<\/p>\n","protected":false},"author":1,"featured_media":14146,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-gradient":""}},"footnotes":""},"categories":[57],"tags":[],"class_list":["post-23780","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sviluppo-software"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/posts\/23780","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/comments?post=23780"}],"version-history":[{"count":1,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/posts\/23780\/revisions"}],"predecessor-version":[{"id":24294,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/posts\/23780\/revisions\/24294"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/media\/14146"}],"wp:attachment":[{"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/media?parent=23780"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/categories?post=23780"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/odc.oimmei.dev\/it\/wp-json\/wp\/v2\/tags?post=23780"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}