Animieren CAShapeLayer Pfad auf animierte Grenzen ändern
Ich habe eine CAShapeLayer (das ist die Schicht, der eine UIView-Unterklasse, deren path
sollte immer aktualisiert, wenn die Ansicht bounds
Größe ändert. Für diese habe ich überschrieben, die Ebene setBounds:
Methode zum zurücksetzen der Pfad:
@interface CustomShapeLayer : CAShapeLayer
@end
@implementation CustomShapeLayer
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.path = [[self shapeForBounds:bounds] CGPath];
}
Dies funktioniert gut, bis ich animieren, die Grenzen zu ändern. Ich würde gerne den Pfad animieren, neben anderen animierte Grenzen ändern (in einem UIView animation-block), so dass die Schicht Form, immer nimmt seine Größe.
Da die path
Eigenschaft nicht animieren standardmäßig, ich kam mit dieser:
Überschreiben der layer - addAnimation:forKey:
Methode. In es, herauszufinden, ob ein bounds-animation wird Hinzugefügt, um die Ebene. Wenn dem so ist, erstellen Sie eine zweite explizite animation für die Animation der Pfad entlang der Grenzen durch das kopieren werden alle Eigenschaften aus den Grenzen der animation, die wird an die Methode übergeben. In code:
- (void)addAnimation:(CAAnimation *)animation forKey:(NSString *)key
{
[super addAnimation:animation forKey:key];
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
if ([basicAnimation.keyPath isEqualToString:@"bounds.size"]) {
CABasicAnimation *pathAnimation = [basicAnimation copy];
pathAnimation.keyPath = @"path";
//The path property has not been updated to the new value yet
pathAnimation.fromValue = (id)self.path;
//Compute the new value for path
pathAnimation.toValue = (id)[[self shapeForBounds:self.bounds] CGPath];
[self addAnimation:pathAnimation forKey:@"path"];
}
}
}
Ich habe diese Idee aus der Lektüre David Rönnqvist ist View-Schicht Synergie Artikel. (Dieser code ist für iOS 8. Auf iOS 7, es scheint, dass Sie haben zu prüfen, die animation ist keyPath
gegen @"bounds"
und nicht " @"Grenzen.Größe".)
Den aufrufenden code, löst die Ansicht animiert Grenzen ändern würde wie folgt Aussehen:
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{
self.customShapeView.bounds = newBounds;
} completion:nil];
Fragen
Dieser funktioniert auch meistens, aber ich habe zwei Probleme mit ihm:
-
Gelegentlich, bekomme ich ein Absturz (Edit: never mind. Ich vergaß zu konvertierenEXC_BAD_ACCESS
) inCGPathApply()
beim auslösen dieser Animationen, während ein vorheriger animation ist noch im Gange. Ich bin mir nicht sicher, ob dies hat nichts zu tun mit meinem speziellen Implementierung.UIBezierPath
zuCGPathRef
. Danke @antonioviero! -
Bei Verwendung der standard-UIView Frühjahr animation, die animation der anzeigen, die Grenzen und der Ebene Weg ist leicht out of sync. Das heißt, der Pfad-animation führt auch zu einer federnden Weg, aber es folgt nicht die Ansicht, die Grenzen genau.
-
Generell ist dies der beste Ansatz? Es scheint, dass mit einer Schicht Form, deren Pfad ist abhängig von seiner Größe und Grenzen, das sollte animieren, in-sync mit einer beliebigen Grenzen ändert, ist etwas, das sollte nur work™, aber ich bin eine harte Zeit. Ich meine, es muss einen besseren Weg geben.
Andere Dinge, die ich versucht habe
- Überschreiben der layer -
actionForKey:
oder die AnsichtactionForLayer:forKey:
um wieder eine eigene animation-Objekt für diepath
Eigenschaft. Ich denke, das wäre der bevorzugte Weg, aber ich habe nicht einen Weg finden, um an der Transaktion die Eigenschaften, die gesetzt werden sollten, das von der umgebenden animation block. Auch, wenn von innen eine animation mit block[CATransaction animationDuration
] etc. immer wieder die default-Werte.
Gibt es einen Weg, um (a) festzustellen, dass Sie derzeit in einem animationsblock, und (b), um die Eigenschaften der animation (Dauer -, Animations-Kurve etc.) festgelegt wurden, werden in diesem block?
Projekt
Hier ist die animation: Das orange Dreieck ist der Pfad der Form-Ebene. Die schwarze Kontur ist die frame-der Ansicht hosting der Formebene.
Haben Sie einen Blick auf das Beispiel-Projekt auf GitHub. (Dieses Projekt ist für iOS 8 und erfordert Xcode 6 zu laufen, sorry.)
Update
Jose Luis Piedrahita wies mich zu dieser Artikel von Nick Lockwood, schlägt die folgende Vorgehensweise vor:
-
Überschreiben der Ansicht
actionForLayer:forKey:
oder der EbeneactionForKey:
Methode und überprüfen, ob die Schlüssel übergeben, die für diese Methode ist die, die Sie animieren möchten (@"path"
). -
Wenn dem so ist, ruft
super
auf einer der Ebene "normal" animierbare Eigenschaften (wie@"bounds"
) wird implizit sagen Sie, wenn Sie in einen animationsblock. Wenn Sie die Ansicht (die Ebene delegieren) gibt ein gültiges Objekt (und nichtnil
oderNSNull
), wir sind. -
Legen Sie die Parameter (Dauer, timing-Funktion, etc.) für die Pfad-animation der animation zurück von
[super actionForKey:]
- und return-Pfad-animation.
Bedeutet ja Super funktionieren unter iOS 7.x. Aber unter iOS 8, wird das Objekt zurückgegeben, die von actionForLayer:forKey:
ist nicht ein standard (und dokumentiert) CA...Animation
aber eine Instanz der privaten _UIViewAdditiveAnimationAction
Klasse (ein NSObject
Unterklasse). Da die Eigenschaften dieser Klasse sind nicht dokumentiert ist, kann ich Sie nicht benutzen einfach einen Pfad erstellen der animation.
_Edit: Oder es könnte funktionieren, nachdem alle. Als Nick erwähnt in seinem Artikel einige Eigenschaften wie backgroundColor
noch zurück, ein standard CA...Animation
auf iOS 8. Ich werde es versuchen out.`
path
ist eine animierbare property. Und ich denke nicht wirklich, dass dies der beste Ansatz. Wie Sie selbst sehen können, sieht es Dünn. Ich hoffe, zu einer Lösung zu kommen in ein paar Minuten.- Ja, aber es nicht animieren, wenn Sie nur den Pfad von innen eine animation mit block. Sie erstellen eine explizite animation, und fügen Sie es auf die Ebene an. Wo sollte ich diese animation, um den Pfad automatisch animiert wird, wenn es verändert wird in einer animation zu sperren?
- Ich würde alles tun, manuell mit CADisplayLink – IMO wäre es beseitigt die meisten Probleme.
- Wenn man sich die animation in slow-mo, so scheint es, dass
CAShapeLayer
's built-in Pfad interpolation ignoriert einfach jeder Fortschritt, Werte, die größer als 1.0 ist, d.h. es unterstützt nicht "überschießen" - Animationen, die erforderlich wäre, für Feder Effekte. Nur eine Beobachtung, nicht sicher, ob das irgendwo dokumentiert, oder ob es möglich, dies zu umgehen. - Das ist meine Beobachtung auch. Wenn log-was passiert, Pfad-animation ist eine korrekt konfigurierte
CASpringAnimation
aber es verhält sich wie eine basic animation. Wenn Sie die gleiche Technik für eine andere Schicht Eigenschaft (wie "Deckkraft" oder "transformieren"), es tut schwingen, als würde man es erwarten. - Für #2 würde ich sagen, dass der Pfad ist nicht elastisch. Es ist nur eine illusion der Grenzen zu springen herum, die macht der Pfad scheint zu springen herum, als gut.
- Danke, das macht Sinn.
- Dazu arbeitet das Projekt in Xcode 5 und iOS 7. Sie erhalten den aktuellen animieren von Eigenschaften, die aus der Ebene
presentationLayer
. @CocoaPriest Auch versucht, mitCADisplayLink
, aber es gelegentlich verursacht ein blauer Bildschirm auf einem 64-bit-Gerät. - Ich weiß nicht, ob es eine richtige Lösung, aber vielleicht könnten Sie nehmen genau die gleiche Vorgehensweise, wie Sie für den Weg selbst und fügen Sie die Animationen nach dem festlegen des Pfades in die Schranken setter.
[super setBounds:bounds]
ist, wo die Animationen Hinzugefügt werden (werden Sie implizit oder UIView-Animationen), so können Sie die erforderlichen Werte aus der Ebene "Grenzen" animation, wenn es vorhanden ist. Leider kann ich bestätigen, aus Erfahrung, dass die Pfad-Animationen eingespannt werden, wenn mit federnden timing-Funktionen. (Beachten Sie auch, dass UIView animation-block und CATransaction sind 2 verschiedene Dinge)
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich weiß, das ist eine alte Frage, aber ich kann Ihnen eine Lösung, die scheint ähnlich zu Mr. Lockwoods Ansatz. Leider ist der source-code hier ist swift so müssen Sie es zu konvertieren, um ObjC.
Wie bereits erwähnt, wenn die Schicht eine Trägerschicht für eine Ansicht, die Sie abfangen können den CAAction ist in der Ansicht selbst. Dies jedoch ist nicht bequem zum Beispiel, wenn die Trägerschicht verwendet wird, in mehr als einer Ansicht.
Die gute Nachricht ist actionForLayer:forKey: ruft tatsächlich actionForKey: in der Trägerschicht.
Es ist in der actionForKey: der Trägerschicht, wo wir Sie abfangen kann diese Anrufe und bieten eine animation, wenn der Pfad geändert wird.
Beispiel layer-geschrieben in swift ist wie folgt:
actionForKey
zuanimationForKey
, und es funktioniertIch glaube, Sie haben Probleme, weil Sie spielen mit dem Rahmen in einer Ebene und es ist Weg in der gleichen Zeit.
Ich würde gehen Sie einfach mit CustomView mit benutzerdefinierten drawRect:, die zieht das, was Sie brauchen, und dann eben
Sor das ist keine Notwendigkeit, Wege zu allen
Hier ist, was ich habe, mit diesem Ansatz
https://dl.dropboxusercontent.com/u/73912254/triangle.mov
Lösung gefunden zu animieren
path
vonCAShapeLayer
währendbounds
animiert:@keypath
Nutzung mit einfachen string-Konstanten. Dies sollte helfen, zu konvertieren, zu Swift.Ich denke, es ist out of sync zwischen Grenzen und Pfad-animation ist, weil verschiedene timing-Funktion zwischen UIVIew Frühjahr und CABasicAnimation.
Vielleicht können Sie versuchen, animieren, transformieren, sondern es soll auch wandeln den Pfad (ungetestet), nachdem es fertig animieren, Sie können dann die gebundenen.
Eine weitere Möglichkeit ist, nehmen Sie die snapshot-Pfad, legen Sie es als Inhalt der Ebene ist, dann animieren Sie die gebunden ist, soll der Inhalt Folgen Sie der animation dann.