{"id":9722,"date":"2013-01-31T10:48:00","date_gmt":"2013-01-31T09:48:00","guid":{"rendered":"http:\/\/www.devapp.it\/wordpress\/?p=9722"},"modified":"2013-02-08T09:41:04","modified_gmt":"2013-02-08T08:41:04","slug":"t112-animazioni-ed-effetti-per-le-nostre-applicazioni-iphone-e-ipad","status":"publish","type":"post","link":"https:\/\/www.devapp.it\/wordpress\/t112-animazioni-ed-effetti-per-le-nostre-applicazioni-iphone-e-ipad\/","title":{"rendered":"T#112 &#8211; Animazioni ed effetti per le nostre applicazioni iPhone e iPad"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/09\/tutorial-beginner-devAPP.jpg\" alt=\"tutorial-beginner-devAPP\" title=\"tutorial-beginner-devAPP\" width=\"200\" height=\"100\" class=\"alignleft size-full wp-image-9514\" \/> In questo nuovo tutorial di programmazione iOS parleremo di un argomento interessante e utile ad ogni sviluppatore iPhone o iPad: le animazioni. Se per un genere di applicazioni queste sono infatti addirittura fondamentali (vedi i videogame) per molte altre possono tornare utili ed essere sfruttate per rendere pi\u00f9 gradevoli le applicazioni mobili che si stanno realizzando. Lungo il tutorial prender\u00f2 ad esempio la mia app &#8220;<a href=\"http:\/\/clk.tradedoubler.com\/click?p=24373&#038;a=1735897&#038;g=0&#038;url=https:\/\/itunes.apple.com\/it\/app\/abc-4-aliens\/id564628544?mt=8&#038;partnerId=2003\" target=\"_blank\">ABC x Alieni<\/a>&#8221; (\u00e8 gratuita per pochi giorni, scaricatela! \ud83d\ude09 ), un gioco per bambini di et\u00e0 prescolare, di cui illustrer\u00f2 e render\u00f2 open source gran parte del codice. Questo gioco mi \u00e8 stato &#8220;commissionato&#8221; dai miei figli e in un primo tempo avevo pensato di utilizzare cocos2D, poi visto che le dinamiche mi sembravano relativamente semplici ho pensato che potesse essere una buona palestra per studiare Core Animation e le animazioni in generale. Questo per un motivo molto semplice: i framework &#8220;game&#8221; mal si adattano alla realizzazione di app &#8220;utility&#8221; o business e anche solo la loro integrazione con l&#8217;UIKit \u00e8 talora farraginosa e poco pratica. Le tecniche illustrate in questo articolo potranno invece facilmente essere utilizzate in qualunque app vogliate sviluppare al fine di renderla pi\u00f9 accattivante e piacevole, requisito ormai fondamentale anche in una semplice to-do list (vedi il successo mondiale dell&#8217;app <a href=\"http:\/\/clk.tradedoubler.com\/click?p=24373&#038;a=1735897&#038;g=0&#038;url=https:\/\/itunes.apple.com\/it\/app\/clear\/id493136154?mt=8&#038;partnerId=2003\" target=\"_blank\">Clear<\/a>)!<!--more--><\/p>\n<p><strong>Nota: <\/strong>Una piccola precisazione prima di iniziare: siete liberi di utilizzare tutto o in parte il codice riportato (possibilmente citando la fonte&#8230;) ma non le immagini (opera di Irene Dapo), che sono fornite al solo titolo di rendere completo il tutorial.<\/p>\n<h4>Il Progetto<\/h4>\n<p>Cominciamo con una breve panoramica. La schermata di gioco dell&#8217;app, presenta due bottoni direzionali su cui viene gestita la pressione continua (quindi non solo il tap singolo), un oggetto in movimento (il carrellino), due alieni che si animano (e alle volte saltano) quando il carrello si muove nella loro direzione, un oggetto che cade dall&#8217;alto. Inoltre muovendo il dispositivo l&#8217;ombra degli oggetti cambia di inclinazione.<\/p>\n<p>La disposizione di partenza degli oggetti nella view \u00e8 fatta usando un normalissimo xib di una UIView di un UIViewController. Rimando alle guide di base la creazione del progetto, del view controller e dello xib. Vi faccio notare per\u00f2 i primi due &#8220;trucchi&#8221; usati: primo i vari oggetti che verranno animati non sono delle UIImageView, ma delle semplici UIView, perch\u00e9 ci interessa definire qui solo l&#8217;ingombro e la posizione, e secondo le &#8220;sprite&#8221; sono messe in modo che gli oggetti siano sempre contro il bordo destro o sinistro, a prescindere dalle dimensioni della view, in modo da ottenere senza sforzo un&#8217;app che si adatti per l&#8217;iPhone 4 o 5. Non \u00e8 sempre possibile, ma spesso con piccoli accorgimenti come questo si pu\u00f2 evitare di dover avere 2 differenti xib per gestire le differenti versioni dello schermo degli iPhone.<\/p>\n<p>La gestione degli eventi avviene nel &#8220;game loop&#8221;, ovvero un ciclo &#8220;infinito&#8221;, scandito da un timer, che ogni decimo di secondo controlla e aggiorna lo stato del gioco. Cos\u00ec facendo ho gi\u00e0 deciso la cadenza degli eventi: aggiornamenti, movimenti e interazioni sono valutate ogni decimo di secondo, un tempo che mi sembrava adatto al tipo di gioco che volevo realizzare: simpatico, non frenetico e &#8220;rilassante&#8221;.<\/p>\n<p>Completano il tutto alcune funzioni per la generazione di numeri casuali (usate per scegliere da quale punto e quale numero far cadere e per decidere quanto in alto far saltare gli alieni) e l&#8217;attivazione della rilevazione dell&#8217;accellerometro: quest&#8217;ultimo non \u00e8 usato per gestire il movimento, ma per dare un tocco pseudo-3D al gioco, aggiungendo delle ombre che si muovano in base all&#8217;inclinazione, come se gli oggetti avessero una profondit\u00e0 e cambiasse l&#8217;inclinazione della luce.<\/p>\n<p>Per comprendere meglio quanto appena descritto e quanto seguir\u00e0 vi consiglio di scaricarvi il progetto allegato (vedi a fondo articolo) e provarlo.<\/p>\n<h4>Tecniche usate: Sprite Sheet<\/h4>\n<p>Una tecnica molto usata per le animazioni nei videogiochi \u00e8 quella dello Sprite Sheet, chiamati anche &#8220;sprite atlas&#8221;, atlanti), ovvero la creazione di una (grossa) immagine contenente tutti i vari &#8220;frame&#8221; di un&#8217;animazione per poi visualizzarne un pezzo per volta. Perch\u00e9 complicarsi la vita vi chiederete? Non \u00e8 pi\u00f9 semplice avere 10 immagini separate e visualizzare di volta in volta quella necessaria? Beh forse \u00e8 pi\u00f9 semplice, ma le prestazioni cambiano drasticamente: ogni volta che si visualizza un&#8217;immagine questa viene caricata nella memoria della GPU (il processore grafico) e questa operazione \u00e8 abbastanza lenta. Mentre mostrare una parte dell&#8217;immagine che gi\u00e0 \u00e8 in memoria \u00e8 di gran lunga pi\u00f9 veloce. Quindi la tecnica migliore consiste nel creare i vari &#8220;fotogrammi&#8221; singolarmente e poi accorparli usando metodi pi\u00f9 o meno ottimizzati. Ovviamente consuma pi\u00f9 memoria, quindi ci vuole un po&#8217; di attenzione.<\/p>\n<p>Esistono Tool professionali (pensati per cocos2D, Corona e altri framework simili) che consentono di &#8220;impacchettare&#8221; all&#8217;estremo le immagini, eliminando le parti trasparenti e memorizzando poi delle mappe di coordinate grazie alle quali si potr\u00e0 ricostruire l&#8217;oggetto iniziale, ma qui vi illustro il metodo pi\u00f9 semplice e &#8220;fai da te&#8221;, ovvero quello in cui tutti i frame hanno la stessa dimensione e orientamento. Cos\u00ec facendo basta sapere quanto \u00e8 grande il frame di riferimento e individuare la porzione giusta dell&#8217;atlante da visualizzare. Non solo, creare uno sprite sheet diviene una semplice operazione di copia-incolla gestibile con Anteprima, senza ricorrere a Photoshop, Gimp o altri programmi specializzati.<\/p>\n<p>Un ultimo dettaglio riguarda le dimensioni. Nella GPU gli sprite sheet posso avere come dimensione solo quadrati con potenze di due di lato: perci\u00f2 avere un atlas rettangolare di 1025 per 500 pixel vuol dire occupare un quadrato di 2048&#215;2048 pixel in memoria! Ma questo \u00e8 solo un limite della GPU, non di Core Animation, quindi in questo tutorial ho volutamente usato spritesheet rettangolari, perch\u00e9 lo scopo era illustrare le potenzialit\u00e0 del framework, anche a livello di semplicit\u00e0: credetemi se non dovete animare troppi oggetti \u00e8 inutile stare ad impazzire per convincere il vostro grafico della necessit\u00e0 di &#8220;schiacciarli&#8221; in pochi pixel \ud83d\ude09<\/p>\n<p>Per gestirli ho rielaborato una classe open source di Miguel Angel Friginal (trovata mentre cercavo altro, come spesso capita), di cui per correttezza ho mantenuto il nome originale. Vediamo come funziona.<\/p>\n<p>Per prima cosa dobbiamo creare uno sprite sheet, o meglio una subclass di CALayer che lo gestisca. La init di questo layer vuole due parametri: l&#8217;immagine con l&#8217;atlas degli sprite e la dimensione del singolo sprite. L&#8217;immagine diventer\u00e0 il contents del layer (che contenendo tutti gli sprite li caricher\u00e0 una volta sola nella memoria della GPU) mentre il CGSize sar\u00e0 usato per definire il bounds (la dimensione) del layer e il suo contentsRect. Quest&#8217;ultimo \u00e8 un parametro un po&#8217; &#8220;strano&#8221; a prima vista, perch\u00e9 \u00e8 un CGFrame che pu\u00f2 contenere solo valori (float) nel range 0.0-1.0. Serve ad indicare quale porzione del contents deve essere realmente visualizzata, ma \u00e8 una misure relativa, dove 0 \u00e8 niente e 1 \u00e8 tutto. <\/p>\n<p>Quindi se gli dico (0.5,0.5),(1,1) gli dico che il quarto in basso a destra dell&#8217;immagine complessiva \u00e8 quello che va visualizzato nel layer:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n- (id)initWithImage:(CGImageRef)img sampleSize:(CGSize)size;{\r\n   \r\n   self = [super init];\r\n\r\n   if (self != nil){\r\n      self.contents = (__bridge id)img;\r\n      self.spriteIndex = 1;\r\n      CGSize sampleSizeNormalized = CGSizeMake(size.width\/CGImageGetWidth(img), size.height\/CGImageGetHeight(img));\r\n      self.bounds = CGRectMake( 0, 0, size.width, size.height);\r\n      self.contentsRect = CGRectMake( 0, 0, sampleSizeNormalized.width, sampleSizeNormalized.height );\r\n   }\r\n\r\n   return self;\r\n}\r\n<\/pre>\n<p>La property &#8220;spriteIndex&#8221; indica quale sprite vogliamo visualizzare (per semplicit\u00e0 di calcolo deve partire da 1. Pertanto quando dobbiamo visualizzare il layer baster\u00e0 calcolare il &#8220;contentsRec&#8221;t in base a quella property e automaticamente verr\u00e0 visualizzato lo sprite desiderato.<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n\/* calcola le coordinate esatte della porzione di sheet da visualizzare *\/\r\n- (void)display;\r\n{\r\nif ([self.delegate respondsToSelector:@selector(displayLayer:)])\r\n{\r\n[self.delegate displayLayer:self];\r\nreturn;\r\n}\r\n\r\nunsigned int currentSampleIndex = [self currentSampleIndex];\r\nif (!currentSampleIndex)\r\nreturn;\r\n\r\nCGSize sampleSize = self.contentsRect.size;\r\nself.contentsRect = CGRectMake(\r\n((currentSampleIndex - 1) % (int)(1\/sampleSize.width)) * sampleSize.width,\r\n((currentSampleIndex - 1) \/ (int)(1\/sampleSize.width)) * sampleSize.height,\r\nsampleSize.width, sampleSize.height\r\n);\r\n}\r\n<\/pre>\n<p>A questo punto non ci resta che usarlo: prima lo creo e posiziono:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\nself.zipSprite = [MCSpriteLayer layerWithImage:[UIImage imageNamed:@\"ZIP2.png\"].CGImage sampleSize:CGSizeMake(100, 132)];\r\nself.zipSprite.position = CGPointMake(50, 66);\r\n[self.zipView.layer addSublayer: self.zipSprite];\r\n<\/pre>\n<p>Quindi, nei punti dove gestisco la logica che sta dietro all&#8217;animazione degli sprite mi baster\u00e0 cambiare l&#8217;indice, ovvero:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[self.zipSprite showFrame:zipIndex];\r\n<\/pre>\n<h4>Ombre e accellerometro<\/h4>\n<p>Spesso l&#8217;accellerometro \u00e8 usato nei giochi per gestire il movimento del personaggio. Io invece l&#8217;ho usato per dare un po&#8217; di senso prospettico al gioco. Gli oggetti in movimento hanno infatti un&#8217;ombra che si muove in base all&#8217;inclinazione del dispositivo, dando un piacevole effetto di profondit\u00e0 alla scena. Le ombre sono una caratteristica fornita dai CALayer e gestibile con semplici property, quindi \u00e8 facile ottenere buoni effetti con poche istruzioni. Vediamole:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n- (void)viewDidAppear:(BOOL)animated{\r\n   [super viewDidAppear:animated];\r\n   \/\/ mi registro agli eventi dell'accellerometro\r\n   UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer];\r\n   accelerometer.delegate = self;\r\n   accelerometer.updateInterval = 0.25;\r\n   ...\r\n}\r\n<\/pre>\n<p>qui informo iOS che sono interessato agli eventi dell&#8217;accellerometro, che mi saranno comunicati in:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{\r\n   CGSize shadow = CGSizeMake(acceleration.y*GAMELOOP_ShadowsRadius,(acceleration.x+acceleration.z)*GAMELOOP_ShadowsRadius);\r\n   self.zipView.layer.shadowOffset = shadow;\r\n   ...\r\n}\r\n<\/pre>\n<p>dove semplicemente calcolo il raggio e l&#8217;inclinazione dell&#8217;ombra e lo imposto a tutti gli oggetti che mi interessano.<\/p>\n<h4>Pressione continua<\/h4>\n<p>iOS non fornisce automaticamente un UIGestureRecognizer per gestire la pressione continua di un bottone: ci sarebbe il &#8220;long press&#8221;, ma in realt\u00e0 non \u00e8 la stessa cosa e non va bene per simulare un joypad. In questo gioco l&#8217;interazione \u00e8 molto semplice, basta premere (e tener premuto&#8230;) uno dei bottoni e il carrellino si muover\u00e0 in quella direzione.<\/p>\n<p>Per ottenere il risultato creo due IBAction e in Interface Builder le assegno ad alcuni eventi significativi dei due bottoni di direzione: theTouchDown all&#8217;event Touch Down e theTouchUp a Touch Up Inside e a Touch Up Outside. In entrambi i metodi setto la variabile globale moveButtonPressed che verr\u00e0 poi utilizzata durante il game loop per sapere se e in che direzione muovere il carrellino.<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n\/* il bottone risulta premuto fino a che non ricevo un touch up *\/\r\n- (IBAction)theTouchDown:(id)sender{\r\n   moveButtonPressed = ((UIButton*)sender).tag;\r\n}\r\n\r\n\/* fine della \"pressione continua\" *\/\r\n - (IBAction)theTouchUp:(id)sender{\r\n   if (((UIButton*)sender).tag == moveButtonPressed) \/\/ solo se il sollevato \u00e8 quello che aveva originato la pressione\r\n      moveButtonPressed = 0;\r\n}\r\n<\/pre>\n<p>In pratica mi &#8220;segno&#8221; solo qual \u00e8 il bottone che il giocatore sta premendo, poi gestir\u00f2 le conseguenza di questa azione nel game loop.<\/p>\n<h4>Movimento<\/h4>\n<p>Qui ho usato un mix di tecniche, cercando sempre la strada pi\u00f9 semplice nel rispetto della fluidit\u00e0 del gioco. Essendo gli oggetti dei CoreAnimationLayer avrei potuto usare le animazioni di quel framework (potenti ma non sempre banali da gestire), ma dato che ogni CALayer \u00e8 incapsulato dentro una UIView e che queste ultime hanno delle animazioni di alto livello facilissime da usare la scelta \u00e8 caduta su queste ultime.<\/p>\n<p>Ad esempio il carrello lo muovo cos\u00ec:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n\/\/ cambio lo sprite da visualizzare\r\n[self.carrelloSprite showFrame:carrelloIndex];\r\n\/\/ e lo muovo: l'animazione viene fluida ma nello stesso tempo a \"scatti\" che sa molto di giochino per bimbi\r\n[UIView animateWithDuration:GAMELOOP_AnimationInterval delay:0 options:UIViewAnimationCurveLinear animations:\r\n^{\r\nself.carrelloView.center = CGPointMake(xpos,self.carrelloView.center.y);\r\n} completion:nil];\r\n<\/pre>\n<p>Mentre per gli alieni che saltano uso semplicemente due animazioni &#8220;accodate&#8221;:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n\/* l'oggetto \u00e8 stato preso, gli alieni saltano felici :) *\/\r\n- (void) zipZapHappy{\r\n   \/\/ faccio fare ai due alieni un salto di altezza casuale\r\n   float zipH = (arc4random()%50)+10;\r\n   float zapH = (arc4random()%50)+10;\r\n   \/\/ l'uso di UIViewAnimationCurveEaseOut per la parte di salita e di easeIn per la discesa rende il salto pi\u00f9 \"naturale\"\r\n   [UIView animateWithDuration:GAMELOOP_AnimationInterval*8 delay:0 options:UIViewAnimationCurveEaseOut animations:\r\n^{\r\nself.zipView.center = CGPointMake(self.zipView.center.x,self.zipView.center.y-zipH);\r\nself.zapView.center = CGPointMake(self.zapView.center.x,self.zapView.center.y-zapH);\r\n} completion:^(BOOL finished) {\r\n[UIView animateWithDuration:GAMELOOP_AnimationInterval*7 delay:0 options:UIViewAnimationCurveEaseIn animations:\r\n^{\r\nself.zipView.center = CGPointMake(self.zipView.center.x,self.zipView.center.y+zipH);\r\nself.zapView.center = CGPointMake(self.zapView.center.x,self.zapView.center.y+zapH);\r\n} completion:nil];\r\n}];\r\n...\r\n}\r\n<\/pre>\n<p>Prima faccio una animazione &#8220;in frenata&#8221; (UIViewAnimationCurveEaseOut) verso l&#8217;alto, poi una &#8220;in accelerazione&#8221; (UIViewAnimationCurveEaseIn) verso il basso: questo unito all&#8217;altezza casuale del salto rende il movimento degli alieni pi\u00f9 naturale, pur senza usare alcun motore fisico come Box2d, Chipmunk e simili.<\/p>\n<p>Da notare che essendo la scelta degli sprite e lo loro visualizzazione indipendente da queste animazioni, se il giocatore preme i bottoni gli alieni si animeranno anche mentre saltano: un effetto speciale completamente gratuito! \ud83d\ude09<\/p>\n<h4>Conclusione<\/h4>\n<p>Spero che questo tutorial vi abbia dato le basi per poter esplorare Core Animation e fornito spunti per impreziosire le UI delle vostre app con effetti e animazioni gradevoli e di effetto. Nel codice ci sono molti commenti che dovrebbero aiutare a comprendere meglio il tutto, ma se avete qualche dubbio o domanda potete aprite un post qui sul <a href=\"http:\/\/forum.devapp.it\/forumdisplay.php?60-I-nostri-articoli\" target=\"_blank\">nostro forum<\/a> gratuito.<\/p>\n<p>Alla prossima<br \/>\n<a href=\"mailto:il.malvagio.dottor.prosciutto@gmail.com\" target=\"_blank\">Il Malvagio Dott. Prosciutto<\/a><br \/>\n(<a href=\"https:\/\/twitter.com\/theEvilDocHam\" target=\"_blank\">@theEvilDocHam<\/a>)<\/p>\n<p style=\"text-align: center;\"><a href=\"#\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2010\/05\/download_icon.png\" alt=\"\" width=\"33\" height=\"40\" align=\"middle\" \/><\/a> Se avete problemi con il progetto presentato, <a href=\"#\" target=\"_blank\">questo \u00e8 il link per scaricare il nostro esempio (in arrivo).<\/a><\/p>\n<p><center><\/p>\n<h4>Vuoi ringraziare l&#8217;autore di questa guida?<br \/>\nOffrigli un caff\u00e8 scaricando <a href=\"http:\/\/clk.tradedoubler.com\/click?p=24373&#038;a=1735897&#038;g=0&#038;url=https:\/\/itunes.apple.com\/it\/app\/abc-4-aliens\/id564628544?mt=8&#038;partnerId=2003\" target=\"_blank\">la sua applicazione<\/a> \ud83d\ude42<\/h4>\n<p><a href=\"http:\/\/clk.tradedoubler.com\/click?p=24373&#038;a=1735897&#038;g=0&#038;url=https:\/\/itunes.apple.com\/it\/app\/abc-4-aliens\/id564628544?mt=8&#038;partnerId=2003\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2013\/01\/ABC-x-Alieni.jpg\" alt=\"ABC-x-Alieni\" width=\"465\" height=\"193\" class=\"aligncenter size-full wp-image-9786\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2013\/01\/ABC-x-Alieni.jpg 465w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2013\/01\/ABC-x-Alieni-300x124.jpg 300w\" sizes=\"auto, (max-width: 465px) 100vw, 465px\" \/><\/a><br \/>\n<\/center><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In questo nuovo tutorial di programmazione iOS parleremo di un argomento interessante e utile ad ogni sviluppatore&#8230;<\/p>\n","protected":false},"author":548,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[757,999,1228,1227,1226,1156,1225],"class_list":["post-9722","post","type-post","status-publish","format-standard","hentry","category-tutorial-pratici","tag-animazioni-xcode","tag-core-animation","tag-ios-dev-tutorial","tag-ios-game-development","tag-sprite-ios","tag-sprite-sheets","tag-spritesheet"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/9722","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/users\/548"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/comments?post=9722"}],"version-history":[{"count":22,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/9722\/revisions"}],"predecessor-version":[{"id":9830,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/9722\/revisions\/9830"}],"wp:attachment":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media?parent=9722"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/categories?post=9722"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/tags?post=9722"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}