Oltre al seguente (in cui prendiamo lo stato corrente dal livello di presentazione, interrompi l'animazione, reimposta lo stato corrente dal livello di presentazione salvato e avvia la nuova animazione), c'è una soluzione molto più semplice.
Se si eseguono animazioni basate su blocchi, se si desidera interrompere un'animazione e avviare una nuova animazione nelle versioni iOS precedenti alla 8.0, è sufficiente utilizzare l'opzione UIViewAnimationOptionBeginFromCurrentState
. (Funzionante in iOS 8, il comportamento predefinito è non solo di partire dallo stato corrente, ma di farlo in un modo che rifletta sia la posizione corrente che la velocità attuale, rendendo in gran parte inutile preoccuparsi di questo problema a tutti . per ulteriori informazioni consultare WWDC 2014 il video Building Interruptible and Responsive Interactions.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
È possibile raggiungere questo fermando l'animazione attuale e iniziare la nuova animazione dalla quale quello attuale lasciato. È possibile farlo con Quartz 2D:
Add QuartzCore.framework to your project se non l'hai già. (Nelle versioni contemporanee di Xcode, spesso non è necessario farlo esplicitamente poiché è automaticamente collegato al progetto.)
Importa l'intestazione necessaria se non l'hai già (ancora una volta, non è necessario nelle versioni contemporanee di Xcode):
#import <QuartzCore/QuartzCore.h>
avere il vostro codice di interrompere l'animazione esistente:
[self.subview.layer removeAllAnimations];
Ottieni un riferimento al livello di presentazione corrente (ovvero lo stato della vista così com'è in questo momento):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Azzerare il transform
(o frame
o altro) in base al valore corrente nel presentationLayer
:
self.subview.layer.transform = currentLayer.transform;
Ora animati da quella transform
(o frame
o qualsiasi altra cosa) per il nuovo valore:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
Mettendo tutto insieme, ecco una routine che commuta la mia scala di trasformazione da 2.0x a ide ntify e ritorno:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
self.subview.layer.transform = currentLayer.transform;
CATransform3D newTransform;
self.large = !self.large;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
O se si voleva passare frame
dimensioni da 100x100 a 200x200 e ritorno:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
CGRect newFrame = currentLayer.frame;
self.subview.frame = currentLayer.frame;
self.large = !self.large;
if (self.large)
newFrame.size = CGSizeMake(200.0, 200.0);
else
newFrame.size = CGSizeMake(100.0, 100.0);
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.frame = newFrame;
}
completion:NULL];
}
Tra l'altro, mentre in genere non ha molta importanza per davvero animazioni rapide, per animazioni lente come la tua, potresti voler impostare la durata dell'animazione di inversione per essere uguale a quanto hai progredito nell'animazione corrente (ad esempio, se sei 0.5 secondi in un'animazione di 3.0 secondi, quando si capovolge, probabilmente non si vuole prendere 3 .0 secondi per invertire quella piccola parte dell'animazione che hai fatto finora, ma piuttosto solo 0,5 secondi). Così, che potrebbe essere simile:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CFTimeInterval duration = kAnimationDuration; // default the duration to some constant
CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time
static CFTimeInterval lastAnimationStart = 0.0; // media time of last animation (zero the first time)
// if we previously animated, then calculate how far along in the previous animation we were
// and we'll use that for the duration of the reversing animation; if larger than
// kAnimationDuration that means the prior animation was done, so we'll just use
// kAnimationDuration for the length of this animation
if (lastAnimationStart)
duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));
// save our media time for future reference (i.e. future invocations of this routine)
lastAnimationStart = currentMediaTime;
// if you want the animations to stay relative the same speed if reversing an ongoing
// reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
// would have been if it was a full animation; if you don't do this, if you repeatedly
// reverse a reversal that is still in progress, they'll incrementally speed up.
if (duration < kAnimationDuration)
lastAnimationStart -= (kAnimationDuration - duration);
// grab the state of the layer as it is right now
CALayer *currentLayer = self.subview.layer.presentationLayer;
// cancel any animations in progress
[self.subview.layer removeAllAnimations];
// set the transform to be as it is now, possibly in the middle of an animation
self.subview.layer.transform = currentLayer.transform;
// toggle our flag as to whether we're looking at large view or not
self.large = !self.large;
// set the transform based upon the state of the `large` boolean
CATransform3D newTransform;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
// now animate to our new setting
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
Grazie Rob. Questa è una soluzione molto buona e completa. Ma funzionerà solo al 100% precisamente con 'UIViewAnimationOptionCurveLinear'. Se utilizzo 'UIViewAnimationOptionCurveEaseInOut', l'animazione inversa non sarà esattamente simmetrica all'animazione originale perché inizierà lentamente e accelera mentre l'animazione originale si interromperà bruscamente. C'è un modo per ricordare in qualche modo lo stato dell'animazione in termini di "velocità" e "accelerazione", vale a dire ottenere il punto corrente nella curva di animazione? – Mischa
@Mischa Ci sarebbero modi per farlo, ma poi sei fuori dal mondo di un'animazione basata su blocchi piacevole e semplice (perché non c'è modo di fare una porzione, ad esempio 87,529%, di una curva di animazione). Sarebbe un lavoro infernale (ad esempio codice dell'anima core personalizzato), per qualcosa che potrebbe non essere osservabile (vedi il mio suggerimento alternativo sotto il quale potrebbe approssimare la tua richiesta), ma certamente si potrebbe fare. Potrei suggerire, invece, che contemplate che se l'animazione è inferiore a, diciamo al 90%, fatta, eseguite 'EaseOut' sul retro (vale a dire, supponendo che vi stiate muovendo rapidamente all'ora), altrimenti 'EaseInOut'. – Rob
@Mischa Detto questo, mi chiedo persino che l'idea di una perfetta simmetria sia del tutto corretta, perché se stai facendo 'EaseInOut' (che significa che stai cercando animazioni fluide), per dire che stai andando per invertire bruscamente l'animazione che risulta essere fatta al 50% (e quindi muoversi alla massima velocità) e ruotarla all'istante e spostarla a velocità massima nell'altro senso, non tanto con un "congedo", wow, sarebbe antitetico all'intenzione originale di 'EaseInOut', no? – Rob