Questa miniserie in due parti ti insegnerà come creare un impressionante effetto di piegatura delle pagine con Core Animation. In questa puntata, imparerai i passaggi necessari per perfezionare la piegatura della pagina creata in precedenza e costruirai un'esperienza di piegatura più interattiva utilizzando i gesti di pizzicamento.
Nella prima parte di questo tutorial in due parti, abbiamo preso un'app di disegno a mano libera che permetteva all'utente di disegnare sullo schermo con il dito e abbiamo implementato un effetto di piegatura 3D sulla tela del disegno che ha dato l'impressione visiva di un libro ripiegato lungo la sua spina dorsale. Abbiamo raggiunto questo utilizzando CALayer
s e trasformazioni. Abbiamo collegato l'effetto a un gesto di tocco che ha provocato il piegamento in incrementi, animando agevolmente tra un incremento e il successivo.
In questa parte del tutorial, cercheremo di migliorare sia gli aspetti visivi dell'effetto (aggiungendo angoli curvi alle nostre pagine e lasciando che le nostre pagine proiettino un'ombra sullo sfondo) sia rendendo più interattivo lo spiegamento pieghevole. , consentendo all'utente di controllarlo con un gesto di pizzicamento. Lungo la strada, impareremo su diverse cose: a CALayer
sottoclasse chiamata CAShapeLayer
, come mascherare un livello per dargli una forma non rettangolare, come abilitare un livello a lanciare un'ombra, come configurare le proprietà dell'ombra e un po 'di più sull'animazione di livello implicita e come rendere piacevoli queste animazioni quando aggiungiamo interazione dell'utente nel mix.
Il punto di partenza per questa parte sarà il progetto Xcode alla fine del primo tutorial. Procediamo!
Ricorda che ciascuna delle pagine sinistra e destra era un'istanza di CALayer
. Erano di forma rettangolare, posti sopra le metà sinistra e destra della tela, lungo la linea verticale che correva attraverso il centro. Abbiamo disegnato il contenuto (cioè lo schizzo a mano libera dell'utente) delle metà sinistra e destra della tela in queste due pagine.
Anche se a CALayer
al momento della creazione inizia con limiti rettangolari (come UIView
s), una delle cose migliori che possiamo fare per i livelli è ritagliare la loro forma secondo una "maschera", in modo che non siano più limitati ad essere rettangolari! Come viene definita questa maschera? CALayer
s hanno un maschera
proprietà che è a CALayer
il cui canale alfa del contenuto descrive la maschera da utilizzare. Se usiamo una maschera "soft" (il canale alfa ha valori frazionari) possiamo rendere il livello parzialmente trasparente. Se usiamo una maschera "dura" (vale a dire con valori alfa zero o uno) possiamo "ritagliare" il livello in modo che raggiunga una forma ben definita di nostra scelta.
Potremmo usare un'immagine esterna per definire la nostra maschera. Tuttavia, poiché la nostra maschera ha una forma specifica (rettangolo con alcuni angoli arrotondati), c'è un modo migliore di farlo in codice. Per specificare una forma per la nostra maschera, usiamo una sottoclasse di CALayer
chiamato CAShapeLayer
. CAShapeLayer
s sono livelli che possono avere qualsiasi forma definita da un tracciato vettoriale del tipo opaco Core Graphics CGPathRef
. Possiamo creare direttamente questo percorso utilizzando l'API Core Graphics basata su C o, più convenientemente, possiamo crearne uno UIBezierPath
oggetto con il framework Objective-C UIKit. UIBezierPath
espone il sottostante CGPathRef
oggetto tramite il suo CGPath
proprietà che può essere assegnata al nostro CAShapeLayer
'S sentiero
proprietà, e questo strato di forma può a sua volta essere assegnato per essere il nostro CALayer
La maschera di s. Fortunatamente per noi, UIBezierPath
può essere inizializzato con molte forme predefinite interessanti, incluso un rettangolo con angoli arrotondati (dove scegliamo quale / i angolo / e arrotondare).
Aggiungi il seguente codice, dopo, ad esempio, la linea rightPage.transform = makePerspectiveTransform ();
nel ViewController.m viewDidAppear:
metodo:
// angoli arrotondati UIBezierPath * leftPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: leftPage.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake (25, 25,0)]; UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPage.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake (25,0, 25,0)]; CAShapeLayer * leftPageRoundedCornersMask = [livello CAShapeLayer]; CAShapeLayer * rightPageRoundedCornersMask = [livello CAShapeLayer]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask;
Il codice dovrebbe essere auto esplicativo. Il percorso di Bezier ha la forma di un rettangolo con le stesse dimensioni del livello mascherato e ha gli angoli appropriati arrotondati (in alto a sinistra e in basso a sinistra per la pagina sinistra, in alto a destra e in basso a destra per la pagina destra).
Costruisci il progetto ed esegui. Gli angoli della pagina dovrebbero essere arrotondati ora ... cool!
Applicare un'ombra è anche facile, ma c'è un intoppo quando vogliamo un'ombra dopo aver applicato una maschera (come abbiamo appena fatto). Ci imbatteremo in questo a tempo debito!
Un CALayer ha un shadowPath
che è un (lo hai indovinato) CGPathRef
e definisce la forma dell'ombra. Un'ombra ha diverse proprietà che possiamo impostare: il suo colore, il suo sfalsamento (in pratica in che modo e quanto lontano cade dal livello), il suo raggio (specificandone l'estensione e la sfocatura) e la sua opacità.
shadowPath
poiché il sistema di disegno elaborerà l'ombra dal canale alfa composto del livello. Tuttavia, questo è meno efficiente e di solito causa problemi alle prestazioni, quindi è sempre consigliabile impostare shadowPath
proprietà quando possibile. Inserisci il seguente blocco di codice immediatamente dopo quello appena aggiunto:
leftPage.shadowPath = [UIBezierPath bezierPathWithRect: leftPage.bounds] .CGPath; rightPage.shadowPath = [UIBezierPath bezierPathWithRect: rightPage.bounds] .CGPath; leftPage.shadowRadius = 100.0; leftPage.shadowColor = [UIColor blackColor] .CGColor; leftPage.shadowOpacity = 0.9; rightPage.shadowRadius = 100.0; rightPage.shadowColor = [UIColor blackColor] .CGColor; rightPage.shadowOpacity = 0.9;
Costruisci ed esegui il codice. Sfortunatamente, non verrà lanciata alcuna ombra e l'output apparirà esattamente come in precedenza. Cosa succede con quello?!
Per capire cosa sta succedendo, commenta il primo blocco di codice che abbiamo scritto in questo tutorial, ma lasciamo il codice shadow in posizione. Adesso costruisci e corri. OK, abbiamo perso gli angoli arrotondati, ma ora i nostri strati proiettano un'ombra nebulosa sulla vista dietro di loro, aumentando il senso di profondità.
Quello che succede è che quando impostiamo a CALayer
La proprietà mask di s, la regione di rendering del livello viene ritagliata nella regione della maschera. Pertanto le ombre (che sono naturalmente proiettate lontano dal livello) non vengono visualizzate e quindi non appaiono.
Prima di tentare di risolvere questo problema, si noti che l'ombra per la pagina destra è stata castata in cima alla pagina sinistra. Questo perché il leftPage
è stato aggiunto alla vista prima rightPage
, quindi il primo è effettivamente "dietro" il secondo nell'ordine di disegno (anche se sono entrambi i livelli fratelli). Oltre a cambiare l'ordine in cui i due livelli sono stati aggiunti al super strato, potremmo cambiare il zPosition
proprietà dei layer per specificare esplicitamente l'ordine di disegno, assegnando un valore float più piccolo al layer che volevamo essere disegnato per primo. Saremmo in un'implementazione più complessa se volessimo eliminare del tutto questo effetto, ma dal momento che (fortunatamente) conferisce un bel effetto ombreggiato alla nostra pagina, siamo contenti di come sono!
Per risolvere questo problema, utilizzeremo due livelli per rappresentare ciascuna pagina, una per generare ombre e l'altra per visualizzare il contenuto disegnato in una regione sagomata. In termini di gerarchia, aggiungeremo i livelli ombra come sottolivello diretto al livello della vista di sfondo. Quindi aggiungeremo i livelli di contenuto ai livelli ombra. Quindi i livelli shadow raddoppieranno come "contenitori" per il livello di contenuto. Tutte le nostre trasformazioni geometriche (relative all'effetto di rotazione della pagina) verranno applicate ai livelli dell'ombra. Dal momento che i livelli di contenuto saranno renderizzati rispetto ai loro contenitori, non dovremo applicare alcuna trasformazione ad essi.
Una volta capito questo, scrivere il codice è relativamente semplice. Ma a causa di tutte le modifiche che stiamo apportando sarà complicato modificare il codice precedente, quindi ti suggerisco di sostituirlo tutti il codice in viewDidAppear:
con il seguente:
- (void) viewDidAppear: (BOOL) animato [super viewDidAppear: animato]; self.view.backgroundColor = [UIColor whiteColor]; leftPageShadowLayer = [livello CAShapeLayer]; rightPageShadowLayer = [livello CAShapeLayer]; leftPageShadowLayer.anchorPoint = CGPointMake (1.0, 0.5); rightPageShadowLayer.anchorPoint = CGPointMake (0.0, 0.5); leftPageShadowLayer.position = CGPointMake (self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); rightPageShadowLayer.position = CGPointMake (self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); leftPageShadowLayer.bounds = CGRectMake (0, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height); rightPageShadowLayer.bounds = CGRectMake (0, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height); UIBezierPath * leftPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: leftPageShadowLayer.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake (25, 25,0)]; UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPageShadowLayer.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake (25,0, 25,0)]; leftPageShadowLayer.shadowPath = leftPageRoundedCornersPath.CGPath; rightPageShadowLayer.shadowPath = rightPageRoundedCornersPath.CGPath; leftPageShadowLayer.shadowColor = [UIColor blackColor] .CGColor; leftPageShadowLayer.shadowRadius = 100.0; leftPageShadowLayer.shadowOpacity = 0.9; rightPageShadowLayer.shadowColor = [UIColor blackColor] .CGColor; rightPageShadowLayer.shadowRadius = 100; rightPageShadowLayer.shadowOpacity = 0.9; leftPage = [CALayer layer]; rightPage = [CALayer layer]; leftPage.frame = leftPageShadowLayer.bounds; rightPage.frame = rightPageShadowLayer.bounds; leftPage.backgroundColor = [UIColor whiteColor] .CGColor; rightPage.backgroundColor = [UIColor whiteColor] .CGColor; leftPage.borderColor = [UIColor darkGrayColor] .CGColor; rightPage.borderColor = [UIColor darkGrayColor] .CGColor; leftPage.transform = makePerspectiveTransform (); rightPage.transform = makePerspectiveTransform (); CAShapeLayer * leftPageRoundedCornersMask = [livello CAShapeLayer]; CAShapeLayer * rightPageRoundedCornersMask = [livello CAShapeLayer]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask; leftPageShadowLayer.transform = makePerspectiveTransform (); rightPageShadowLayer.transform = makePerspectiveTransform (); curtainView = [[UIView alloc] initWithFrame: self.view.bounds]; curtainView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor]; [curtainView.layer addSublayer: leftPageShadowLayer]; [curtainView.layer addSublayer: rightPageShadowLayer]; [leftPageShadowLayer addSublayer: leftPage]; [rightPageShadowLayer addSublayer: rightPage]; UITapGestureRecognizer * foldTap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (fold :)]; [self.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (unfold :)]; unfoldTap.numberOfTouchesRequired = 2; [self.view addGestureRecognizer: unfoldTap];
È inoltre necessario aggiungere le due variabili di istanza corrispondenti ai due livelli di ombreggiatura. Modificare il codice all'inizio del @implementazione
sezione da leggere:
@implementation ViewController CALayer * leftPage; CALayer * rightPage; UIView * curtainView; CAShapeLayer * leftPageShadowLayer; CAShapeLayer * rightPageShadowLayer;
Sulla base della nostra discussione precedente, dovresti trovare il codice semplice da seguire.
Ricordiamo che in precedenza ho detto che i livelli contenenti il contenuto disegnato sarebbero contenuti come sottolivelli al livello di generazione di ombre. Pertanto, dobbiamo modificare il nostro piegare:
e svolgersi:
metodi per eseguire la trasformazione richiesta sui livelli ombra.
- (void) fold: (UITapGestureRecognizer *) gr // disegna la bitmap "incrementalImage" nei nostri strati CGImageRef imgRef = ((CanvasView *) self.view) .incrementalImage.CGImage; leftPage.contents = (__bridge id) imgRef; rightPage.contents = (__bridge id) imgRef; leftPage.contentsRect = CGRectMake (0.0, 0.0, 0.5, 1.0); // questo rettangolo rappresenta la metà sinistra dell'immagine rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // questo rettangolo rappresenta la metà destra dell'immagine leftPageShadowLayer.transform = CATransform3DScale (leftPageShadowLayer.transform, 0.95, 0.95, 0.95); rightPageShadowLayer.transform = CATransform3DScale (rightPageShadowLayer.transform, 0,95, 0,95, 0,95); leftPageShadowLayer.transform = CATransform3DRotate (leftPageShadowLayer.transform, D2R (7.5), 0.0, 1.0, 0.0); rightPageShadowLayer.transform = CATransform3DRotate (rightPageShadowLayer.transform, D2R (-7.5), 0.0, 1.0, 0.0); [self.view addSubview: curtainView]; - (void) unfold: (UITapGestureRecognizer *) gr leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); // uncomment later rightpageShadowLayer.transform = makePerspectiveTransform (); // decommenta più tardi [curtainView removeFromSuperview];
Crea ed esegui l'app per controllare le nostre pagine con gli angoli arrotondati e l'ombra!
Come in precedenza, i tocchi con un dito fanno piegare il libro in incrementi, mentre il ripristino con due dita del mouse rimuove l'effetto e ripristina l'app nella normale modalità di disegno.
Le cose stanno andando bene, visivamente parlando, ma il rubinetto non è davvero un gesto realistico per una metafora del libro pieghevole. Se pensiamo alle app per iPad come Carta, il piegamento e lo spiegamento sono guidati da un gesto di pizzicamento. Implementiamolo adesso!
Implementare il seguente metodo in ViewController.m:
- (void) foldWithPinch: (UIPinchGestureRecognizer *) p if (p.state == UIGestureRecognizerStateBegan) // ... (A) self.view.userInteractionEnabled = NO; CGImageRef imgRef = ((CanvasView *) self.view) .incrementalImage.CGImage; leftPage.contents = (__bridge id) imgRef; rightPage.contents = (__bridge id) imgRef; leftPage.contentsRect = CGRectMake (0.0, 0.0, 0.5, 1.0); rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); rightPageShadowLayer.transform = makePerspectiveTransform (); [self.view addSubview: curtainView]; float scale = p.scale> 0.48? p.scale: 0,48; // ... (B) scala = scala < 1.0 ? scale : 1.0; // SOME CODE WILL GO HERE (1) leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform(); rightPageShadowLayer.transform = makePerspectiveTransform(); leftPageShadowLayer.transform = CATransform3DScale(leftPageShadowLayer.transform, scale, scale, scale); // (C) rightPageShadowLayer.transform = CATransform3DScale(rightPageShadowLayer.transform, scale, scale, scale); leftPageShadowLayer.transform = CATransform3DRotate(leftPageShadowLayer.transform, (1.0 - scale) * M_PI, 0.0, 1.0, 0.0); rightPageShadowLayer.transform = CATransform3DRotate(rightPageShadowLayer.transform, -(1.0 - scale) * M_PI, 0.0, 1.0, 0.0); // SOME CODE WILL GO HERE (2) if (p.state == UIGestureRecognizerStateEnded) //… (C) // SOME CODE CHANGES HERE LATER (3) self.view.userInteractionEnabled = YES; [curtainView removeFromSuperview];
Una breve spiegazione riguardante il codice, rispetto alle etichette A, B e C di cui al codice:
stato
proprietà prendendo il valore UIGestureRecognizerStateBegan
) iniziamo a preparare l'animazione per piega. La dichiarazione self.view.userInteractionEnabled = NO;
assicura che eventuali tocchi aggiuntivi che si verificano durante il gesto di pizzicamento non causino il disegno sulla tela. Il codice rimanente dovrebbe esserti familiare. Stiamo solo ripristinando le trasformazioni di livello.scala
la proprietà del pizzico determina il rapporto della distanza tra le dita rispetto all'inizio del pizzico. Ho deciso di bloccare il valore che useremo per calcolare le trasformazioni di ridimensionamento e rotazione delle nostre pagine 0,48. La condizione la scala è tale che un "pizzico inverso" (l'utente muove le dita più distanti rispetto all'inizio del pizzico, corrispondente a p.scale> 1.0
) non ha alcun effetto. La condizione p.scale> 0,48
è così che quando la distanza inter-dito diventa circa la metà di ciò che era all'inizio del pizzico, la nostra animazione pieghevole è completata e ogni ulteriore pizzico non ha alcun effetto. Scelgo 0,48 invece di 0,50 a causa del modo in cui calcolo l'angolo di virata della trasformazione rotazionale del livello. Con un valore di 0,48 l'angolo di rotazione sarà leggermente inferiore a 90 gradi, quindi il libro non si piega completamente e quindi non diventerà invisibile.
Aggiungi il codice per aggiungere un riconoscimento pizzico alla fine di viewDidAppear:
UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer alloc] initWithTarget: self action: @selector (foldWithPinch :)]; [self.view addGestureRecognizer: pizzico];
Puoi sbarazzarti di tutto il codice relativo ai due riconoscitori tocco ViewController.m perché non avremo più bisogno di quelli.
Costruisci ed esegui. Se esegui il test sul simulatore anziché sul dispositivo, ricorda che è necessario tenere premuto il tasto di opzione per simulare i movimenti di due dita come il pizzicamento.
In linea di principio, il nostro spiegamento pieghevole a pinch funziona, ma sei obbligato a notare (in particolare se stai provando su un dispositivo fisico) che l'animazione è lenta e resta indietro rispetto al pizzico, indebolendo quindi l'illusione che il pizzico sta guidando l'animazione pieghevole-spiegante. Quindi cosa sta succedendo?
Ricorda l'animazione implicita della trasformazione che stavamo godendo "gratuitamente" quando l'animazione era controllata dal rubinetto? Bene, si scopre che la stessa animazione implicita è diventata un ostacolo ora! In generale, quando vogliamo controllare un'animazione con un gesto, come una vista trascinata sullo schermo con un gesto di trascinamento (panning) (o il caso con la nostra app qui), vogliamo annullare le animazioni implicite. Assicuriamoci di capire perché, considerando il caso di un utente che trascina una vista sullo schermo con il dito:
vista
è in posizione p0
per cominciare (es. view.position = p0
dove p0 = (x0, y0) ed è a CGPoint
)p1
.vista
a partire dal p0
a p1
con la durata "rilassata" di 0,25 secondi. Tuttavia, l'animazione è appena iniziata e l'utente ha già trascinato il dito su una nuova posizione, p2
. L'animazione deve essere annullata e una nuova inizia verso la posizione p2.Dal momento che il gesto di panning dell'utente (nel nostro esempio ipotetico) è effettivamente continuo, abbiamo solo bisogno di cambiare la posizione della vista al passo con il gesto dell'utente per mantenere l'illusione di un'animazione di risposta! Esattamente lo stesso argomento vale per la nostra pagina piegata con situazione di pizzico qui. Ho solo menzionato l'esempio di trascinamento perché sembrava più semplice spiegare cosa stava succedendo.
Esistono diversi modi per disabilitare le animazioni implicite. Scegliamo il più semplice, che prevede il wrapping del nostro codice in a CATransaction
blocca e invoca il metodo di classe [CATransaction setDisableActions: YES]
. Quindi, che cosa è un CATransaction
Comunque? In termini più semplici, CATransaction
"raggruppa" le modifiche alle proprietà che devono essere animate e successivamente aggiorna i loro valori sullo schermo, gestendo tutti gli aspetti del timing. In effetti, fa tutto il duro lavoro relativo al rendering delle nostre animazioni sullo schermo per noi! Anche se non abbiamo ancora usato esplicitamente una transazione di animazione, una implicita è sempre presente quando viene eseguito un codice di animazione. Quello che dobbiamo fare ora è avvolgere l'animazione con un'abitudine CATransaction
bloccare.
Nel pinchToFold:
metodo, aggiungi queste righe:
[Inizia la CATransaction]; [CATransaction setDisableActions: YES];
Al sito del commento // ALCUNI CODICI VERRANNO QUI (1)
,
aggiungi la linea:
[Commit di CATransaction];
Se ora costruisci ed esegui l'app, noterai che lo spiegamento pieghevole è molto più fluido e reattivo!
Un altro problema relativo alle animazioni che dobbiamo affrontare è quello nostro dispiegarsi:
il metodo non anima il ripristino del libro al suo stato appiattito quando termina il gesto di pizzicamento. Potresti lamentarti del fatto che nel nostro codice non ci siamo davvero preoccupati di ripristinare il trasformare
nel "quindi" blocco del nostro if (p.state == UIGestureRecognizerStateEnded)
dichiarazione. Ma puoi provare a inserire le istruzioni che ripristinano la trasformazione del livello prima dell'istruzione [canvasView removeFromSuperview];
per vedere se i livelli animano questa modifica delle proprietà (spoiler: non lo faranno!).
Il motivo per cui i livelli non animano il cambiamento di proprietà è che qualsiasi modifica di proprietà che potremmo eseguire in quel blocco di codice sarebbe concentrata nello stesso (implicito) CATransaction
come codice per rimuovere la vista di hosting del livello (canvasView
) dallo schermo. La rimozione della vista avverrebbe immediatamente - non si tratta di un cambiamento animato, dopotutto - e non si presenterebbe alcuna animazione in nessuna sottoview (o sottolivellista aggiunto al suo livello).
Di nuovo, un esplicito CATransaction
il blocco viene in nostro soccorso! UN CATransaction
ha un blocco di completamento che viene eseguito solo dopo eventuali modifiche alle proprietà che appaiono dopo aver terminato l'animazione.
Cambia il codice seguendo il if (p.state == UIGestureRecognizerStateEnded)
clausola, in modo che la dichiarazione if reciti come segue:
if (p.state == UIGestureRecognizerStateEnded) [CATransaction begin]; [CATransaction setCompletionBlock: ^ self.view.userInteractionEnabled = YES; [curtainView removeFromSuperview]; ]; [CATransaction setAnimationDuration: 0.5 / scale]; leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; [Commit di CATransaction];
Nota che ho deciso di cambiare la durata dell'animazione in modo inversamente rispetto a scala
in modo che maggiore è il grado della piega nel momento in cui termina il gesto, più tempo dovrebbe subentrare l'animazione di ripristino.
La cosa fondamentale da capire qui è che solo dopo il trasformare
le modifiche hanno animato il codice nel file completionBlock
eseguire. Il CATransaction
la classe ha altre proprietà che puoi usare configurare un'animazione esattamente come vuoi tu. Ti suggerisco di dare un'occhiata alla documentazione di più.
Costruisci ed esegui. Infine, la nostra animazione non sembra solo buona, ma risponde correttamente all'interazione dell'utente!
Spero che questo tutorial ti abbia convinto che i Core Animation Layers sono una scelta realistica per realizzare effetti 3D e animazioni piuttosto sofisticati con poco sforzo. Con un po 'di ottimizzazione, dovresti essere in grado di animare alcune centinaia di livelli sullo schermo allo stesso tempo, se necessario. Un ottimo caso d'uso per Core Animation consiste nell'incorporare una fantastica transizione 3D quando passi da una vista all'altra nella tua app. Sento anche che Core Animation potrebbe essere una valida opzione per creare semplici giochi basati su parole o basati su carte.
Anche se il nostro tutorial si estendeva su due parti, abbiamo appena scalfito la superficie di livelli e animazioni (ma si spera che sia una buona cosa!). Ci sono altri tipi di interessanti CALayer
sottoclassi che non abbiamo avuto la possibilità di guardare. L'animazione è un argomento enorme in sé. Raccomando di guardare i discorsi del WWDC su questi argomenti, come "Core Animation in Practice" (sessioni 424 e 424, WWDC 2010), "Core Animation Essentials" (Session 421, WWDC 2011), "Performance app iOS - Grafica e animazioni" (Sessione 238, WWDC 2012) e "Ottimizzazione della grafica 2D e delle prestazioni di animazione" (Sessione 506, WWDC 2012), quindi ricerca nella documentazione. Apprendimento felice e scrittura di app!