Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Die Reise nach iOS
3 Sehen und anfassen
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Multimedia
10 Jahrmarkt der Nützlichkeiten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Apps programmieren für iPhone und iPad von Klaus M. Rodewig, Clemens Wagner
Das umfassende Handbuch
Buch: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 6 Models, Layer, Animationen
Pfeil 6.1 Modell und Controller
Pfeil 6.1.1 iOS Next Topmodel
Pfeil 6.1.2 View an Controller
Pfeil 6.1.3 Gerätebewegungen auswerten
Pfeil 6.1.4 Modell an Controller
Pfeil 6.1.5 Undo und Redo
Pfeil 6.1.6 Unit-Tests
Pfeil 6.2 Als die Views das Laufen lernten
Pfeil 6.2.1 Animationen mit Blöcken
Pfeil 6.2.2 Transitionen
Pfeil 6.2.3 Zur Animation? Bitte jeder nur einen Block!
Pfeil 6.3 Core Animation
Pfeil 6.3.1 Layer
Pfeil 6.3.2 Vordefinierte Layer-Klassen
Pfeil 6.3.3 Der Layer mit der Maske
Pfeil 6.3.4 Unser Button soll schöner werden
Pfeil 6.3.5 Spieglein, Spieglein an der Wand
Pfeil 6.3.6 Der bewegte Layer
Pfeil 6.3.7 Daumenkino
Pfeil 6.3.8 Relativitätstheorie
Pfeil 6.3.9 Der View, der Layer, seine Animation und ihr Liebhaber
Pfeil 6.3.10 Transaktionen
Pfeil 6.3.11 Die 3. Dimension
Pfeil 6.4 Scrollviews und gekachelte Layer
Pfeil 6.4.1 Scrollen und Zoomen
Pfeil 6.4.2 Die Eventverarbeitung
Pfeil 6.4.3 Scharfe Kurven
Pfeil 6.4.4 Ganz großes Kino
Pfeil 6.4.5 PDF-Dateien anzeigen
Pfeil 6.5 Über diese Brücke musst du gehen
Pfeil 6.5.1 Toll-free Bridging und ARC
Pfeil 6.5.2 C-Frameworks und ARC
Pfeil 6.6 Was Sie schon immer über Instruments wissen wollten, aber nie zu fragen wagten
Pfeil 6.6.1 Spiel mir das Lied vom Leak
Pfeil 6.6.2 Ich folgte einem Zombie
Pfeil 6.6.3 Time Bandits
Pfeil 6.6.4 Instruments und der Analyzer

Rheinwerk Computing - Zum Seitenanfang

6.3Core AnimationZur nächsten Überschrift

Auf den ersten Blick wirken die View-Animationen ein bisschen wie Magie. Durch einen zusätzlichen Methodenaufruf verwandeln Sie eine statische Veränderung des Views in einen eleganten Übergang, wobei Sie auch noch eine große Palette möglicher Animationen zur Verfügung haben. Da fragt sich doch der neugierige Programmierer, wie das funktioniert. Die Antwort darauf findet sich eine Ebene tiefer.

Core Animation ist eine Sammlung von Klassen zur Darstellung, Projektion und Animation von Grafiken. Der Name Core Animation ist dabei vielleicht etwas verwirrend, da Sie Core Animation auch ohne Animationen sinnvoll verwenden können.

Core Animation verwenden

Wenn Sie die Klassen aus Core Animation in Ihren Apps verwenden möchten, müssen Sie das QuartzCore-Framework einbinden. Dazu wählen Sie im Projektnavigator das Projekt aus und danach unter Targets das entsprechende Target. Dort gehen Sie in die Rubrik Linked Frameworks and Libraries unter den Reiter General. Wenn Sie den Plusknopf am unteren Rand der Liste anklicken, können Sie das Framework über den Eintrag QuartzCore.framework auswählen und zu dem Target hinzufügen. Außerdem müssen Sie den entsprechenden Header über

#import <QuartzCore/QuartzCore.h>

in Ihre Quelldateien importieren, damit der Compiler Ihren Programmcode übersetzen kann.


Rheinwerk Computing - Zum Seitenanfang

6.3.1LayerZur nächsten ÜberschriftZur vorigen Überschrift

Ein wichtiger Bestandteil von Core Animation sind Layer, die auch die Views des UIKits für ihre Darstellung verwenden. Ein Layer ist eine Zeichenebene, die Inhalte für die Anzeige bereitstellt, und seine Basisklasse ist CALayer. Dabei ist jedem View ein Layer zugeordnet, den Sie über die Property layer erhalten. Nach der Erzeugung eines Views ist sein Layer fest, und Sie können dem View keinen neuen Layer zuweisen. Das ist im Allgemeinen auch nicht nötig, da Sie ja beliebige Grafiken über den View zeichnen können (siehe Kapitel 3, »Sehen und anfassen«).

Die Klasse UIView hat allerdings die Klassenmethode layerClass, über die sie die Klasse ihres Layers bestimmt; normalerweise ist das die Klasse CALayer. Sie können in Ihren eigenen Unterklassen jedoch diese Methode überschreiben, um so die Layer-Klasse des Views festlegen zu können. Das Beispielprojekt Pie enthält die Klasse PieView, die für die Darstellung die Layer-Klasse PieLayer verwendet. Listing 6.35 enthält die Implementierung der Methode layerClass, um diese Zuordnung zu realisieren.

+ (Class)layerClass {
return [PieLayer class];
}

Listing 6.35 Festlegen der Layer-Klasse eines Views

Projektinformation

Den Quellcode des Beispielprojekts Pie finden Sie auf der DVD unter Code/Apps/iOS5/Pie oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS5/Pie.

Layer können, analog zu Views, Sublayer haben. Wenn ein View Subviews hat, dann sind deren Layer die Sublayer des Layers des Views. Eine grafische Veranschaulichung dafür finden Sie in Abbildung 6.16.

Abbildung

Abbildung 6.16 View- und Layer-Hierarchie

Jeder View hat zwar einen Layer, nicht jeder Layer muss dagegen zu einem View gehören. Sie können also in einen Layer beliebig viele Sublayer einfügen. Die Verwaltung der Sublayer erfolgt dabei analog zu Views. Sie erhalten alle Sublayer eines Layers über die Property sublayers. Durch die Methode addSublayer: können Sie neue Sublayer zu einem Layer hinzufügen, und über die Property superlayer haben Sie Zugriff auf den darüberliegenden Layer.

Layer haben noch eine Reihe weiterer Eigenschaften. Sie finden im Layer viele Propertys, die Sie schon von der Klasse UIView kennen. Teilweise haben sie jedoch auch andere Namen. Beispielsweise heißt die analoge Methode zu clipsToBounds des Views im Layer masksToBounds, und das Gegenstück zu der View-Property alpha im Layer ist opacity.

Sehr praktisch ist beispielsweise die Property cornerRadius der Klasse CALayer. Damit können Sie die Ecken eines Layers abrunden. Dafür müssen Sie den Property-Wert größer als 0 setzen. Außerdem können Sie einen Schatten, eine Rahmenfarbe und eine Rahmenbreite wie beispielsweise in Listing 6.36 festlegen:

theLayer.cornerRadius = 10.0;
theLayer.borderColor = [UIColor blueColor].CGColor;
theLayer.borderWidth = 2.0;
theLayer.shadowColor = [UIColor blackColor].CGColor;
theLayer.shadowOffset = CGSizeMake(5.0, 5.0);
theLayer.shadowOpacity = 0.5;

Listing 6.36 Erzeugung eines Layer-Rahmens

Core Animation und das UIKit

Wie Sie an Listing 6.36 sehen, verwenden Layer Core-Graphics-Farben und keine UIColor-Objekte. Da das UIKit auf Core Animation basiert, dürfen die Komponenten aus Letzterem auch nicht Komponenten des UIKits verwenden, da das zu zyklischen Abhängigkeiten zwischen diesen Frameworks führte. Core Animation verwendet also immer die entsprechenden Komponenten aus Core Graphics: also beispielsweise CGColor, CGImage, CGFont und CGPath anstatt UIColor, UIImage, UIFont beziehungsweise UIBezierPath. Die Klassen des UIKits erlauben in der Regel auch einen Zugriff auf die entsprechende Core-Graphics-Komponente.

Wenn Sie die Propertys wie dort setzen, erhalten Sie einen Rahmen um den Layer, wie ihn Abbildung 6.17 darstellt. Sie können diese Propertys natürlich auch bei dem Layer eines Views setzen, um die Ecken eines rechteckigen Views abzurunden. Das ist eine einfache Möglichkeit, die Gestaltung der Oberflächen Ihrer Apps aufzulockern.

Ein Layer kann auch ein Delegate haben, das bei den Standard-Layern der Views immer der View ist. Sie dürfen weder diesen Layern ein anderes Delegate zuweisen noch den View als Delegate für einen anderen Layer verwenden. Bei allen anderen Layern können Sie hingegen das Delegateobjekt frei wählen und über die Property delegate setzen.

Informelles Protokoll

Es gibt kein formales Protokoll für das Layer-Delegate. Die Deklaration befindet sich hingegen in der Kategorie NSObject(CALayerDelegate) in der Headerdatei der Layer-Klasse. Diese Art, Delegate-Methoden zu deklarieren, heißt auch informelles Protokoll, und Sie finden die Dokumentation dieser Methoden unter »CALayerDelegate Informal Protocol Reference«.

Es gibt drei mögliche Wege, den Inhalt eines Layers bereitzustellen. Über die Property contents können Sie dem Layer ein Core-Graphics-Bild (CGImageRef) zuweisen. Eine solche Referenz erhalten Sie beispielsweise über die Property CGImage eines Bildes der Klasse UIImage. Apple empfiehlt, diese Zuordnung in der Delegate-Methode displayLayer: vorzunehmen. In Listing 6.38 sehen Sie ein Beispiel, wie Sie den Inhalt über ein Bild bereitstellen können.

- (void)displayLayer:(CALayer *)inLayer {
UIImage *theImage = [UIImage imageNamed:@"image.png"];

theLayer.contents = theImage.CGImage;
}

Listing 6.37 Setzen der »contents«-Property eines Layers

Der Layer-Inhalt lässt sich hingegen auch durch Zeichenoperationen erzeugen. Sie können dafür entweder eine Delegate-Methode verwenden oder eine Unterklasse erstellen. Für die Delegation implementieren Sie die Methode drawLayer:inContext:, wobei Sie den Kontextparameter für Ihre Zeichenoperationen verwenden:

- (void)drawLayer:(CALayer *)inLayer  
inContext:(CGContextRef)inContext {
CGContextSaveGState(inContext);
// beliebige Zeichenoperationen
CGContextRestoreGState(inContext);
}

Listing 6.38 Inhalt eines Layers über Delegate-Methode zeichnen

Cocoa Touch ruft die Methode drawRect: über drawLayer:inContext: auf. Sie haben also bereits – ohne es zu ahnen – Layer-Inhalte über Delegation erzeugt.

»UIView« und »drawLayer:inContext:«

Wenn Sie den Viewinhalt über die Delegate-Methode anstatt mit drawRect: zeichnen möchten, dann müssen Sie trotzdem die Methode drawRect: überschreiben. Ansonsten ruft Cocoa Touch die Delegate-Methode nicht auf. Es reicht indes aus, wenn Sie die Methode leer lassen.

Der dritte Weg zur Bereitstellung des Layer-Inhalts ist das Überschreiben der Methode drawInContext: in einer Unterklasse von CALayer. Auch hier bekommen Sie den Kontext zum Zeichnen als Parameter übergeben.

Das Beispielprojekt Pie auf der beiliegenden DVD stellt ein Kreissegment dar, dessen Ausschnitt Sie über einen Schieberegler verändern können (siehe Abbildung 6.17). Die Darstellung des Segments basiert dabei auf einem Layer mit folgender Implementierung der Methode drawInContext:

- (void)drawInContext:(CGContextRef)inContext {
CGRect theBounds = self.bounds;
CGSize theSize = theBounds.size;
CGFloat thePart = self.part;
CGPoint theCenter = CGPointMake(CGRectGetMidX(theBounds),
CGRectGetMidY(theBounds));
CGFloat theRadius = fminf(theSize.width, theSize.height) /
2.0 – 5.0;
CGFloat theAngle = 2 * (thePart – 0.25) * M_PI;

CGContextSaveGState(inContext);
CGContextSetFillColorWithColor(inContext,
[UIColor redColor].CGColor);
CGContextMoveToPoint(inContext, theCenter.x, theCenter.y);
CGContextAddArc(inContext, theCenter.x, theCenter.y,
theRadius, -M_PI / 2.0, theAngle, NO);
CGContextAddLineToPoint(inContext, theCenter.x,
theCenter.y);
CGContextFillPath(inContext);
CGContextRestoreGState(inContext);
}

Listing 6.39 Zeichnen des Kreissegments im Layer

Abbildung

Abbildung 6.17 Das »Pie«-Beispielprogramm

Die Property part enthält die Größe des Kreissegments als Wert zwischen 0 und 1. Ein Layer kann über die Methoden valueForKey: und setValue:forKey: beliebige Werte speichern, und die Klasse PieLayer speichert den Wert für die Property part auch über diese Methoden. Die Deklaration dieser Property in der Klasse PieLayer erfolgt zwar über ein gewohntes

@property (nonatomic) CGFloat part;

die Implementierung erfolgt hingegen über die Anweisung @dynamic part;, wodurch der Layer die beiden Methoden valueForKey: und setValue:forKey: für den Getter beziehungsweise Setter mit dem Schlüssel »part« verwendet.

Achtung bei automatisch synthetisierten Propertys

Mit Xcode 4.5 hat sich das Verhalten des Compilers geändert: In den älteren Versionen konnten Sie die @dynamic-Anweisung auch einfach weglassen. Der Compiler spuckte dann zwar eine Warnung aus, das Programm funktionierte indes trotzdem. Xcode 4.5 nimmt bei fehlender Implementierungsanweisung für eine Property an, dass die Applikation sie synthetisieren soll. Die @dynamic-Anweisung ist ab Xcode 4.5 also notwendig.

Durch die dynamische Implementierung der Property ruft die Laufzeitumgebung immer, wenn Sie lesend oder schreibend auf part zugreifen, die Methoden valueForKey: beziehungsweise setValue:forKey: mit @"part" als Schlüssel auf. Dabei packt sie den Fließkommawert immer schön aus dem NSNumber-Objekt aus beziehungsweise in ein solches Objekt ein.

Layer-Eigenschaften über Propertys

Legen Sie die Propertys eines Layers möglichst immer dynamisch an. Sie können zwar auch synthetisierte Propertys verwenden oder eigene Implementierungen dafür schreiben. Allerdings dürfen Sie in der Implementierung nicht die Key-Value-Coding-Methoden mit dem Property-Namen als Schlüssel verwenden. Die Implementierung

- (CGFloat)part {
return [[self valueForKey:@"part"] floatValue];
}

führt zu einer Endlosrekursion, da der Aufruf von valueForKey: wieder die Methode part aufruft. Sie bekommen indes weiteren Ärger, wenn Sie diese Propertys animieren wollen. Also nutzen Sie lieber das Key-Value-Coding. Das ist ein Angebot, das Sie einfach nicht ablehnen können.

Natürlich sollte der Layer seine Propertys auch mit Werten vorbelegen. Dafür stellt die Klasse CALayer die Klassenmethode defaultValueForKey: zur Verfügung, die Sie überschreiben können. Sie liefert für die Schlüssel des Layers entweder den Standardwert zurück oder reicht den Aufruf an die Methode der Oberklasse weiter. Da diese Methode den Rückgabetyp id hat, muss sie den Standardwert für die Property part als Objekt liefern.

static NSString * const kPartKey = @"part";

+ (id)defaultValueForKey:(NSString *)inKey {
return [kPartKey isEqualToString:inKey] ?
@0.0f : [super defaultValueForKey:inKey];
}

Listing 6.40 Standardwert für die Property »part«

Wenn Sie jetzt die Klasse PieLayer verwenden, sehen Sie noch nichts – zumindest kein Tortenstück. Das könnte daran liegen, dass die Größe des Segments den Wert 0 hat; jedoch selbst mit einem anderen Standardwert für part gibt’s immer noch keinen Kuchen, und die Fläche bleibt immer noch weiß.

Das kommt daher, dass niemand dem Layer gesagt hat, dass er etwas zeichnen soll. Sie können das erreichen, indem Sie ihm die Nachricht setNeedsDisplay schicken. Das müssen Sie immer machen, wenn Sie den Wert für part verändern. Das ist allerdings sehr unpraktisch, da Sie dafür entweder die Methode setPart: implementieren oder KVO verwenden müssten.

Um dieses Problem zu umgehen, bietet die Layer-Klasse über die Klassenmethode needsDisplayForKey: eine einfachere Möglichkeit an. Core Animation zeichnet den Layer bei einer Änderung eines Property-Wertes automatisch neu, wenn die Methode für den Property-Namen YES liefert. Sie können dazu diese Methode in Ihren Klassen einfach wie in Listing 6.41 überschreiben.

+ (BOOL)needsDisplayForKey:(NSString *)inKey {
return [kPartKey isEqualToString:inKey] ||
[super needsDisplayForKey:inKey];
}

Listing 6.41 Layer bei Änderung des Property-Wertes neu zeichnen

Layer-Propertys gesucht

Core Animation ruft die Methode needsDisplayForKey: einmal für jede Property der Layer-Klasse auf. Sie sollten auch deshalb die Layer-Eigenschaften als dynamische Property deklarieren. Für Eigenschaften, die Sie nur über Getter und Setter deklariert haben, ruft Core Animation needsDisplayForKey: hingegen nicht auf. Überschreiben Sie also lieber die Methode needsDisplayForKey:.

Irgendwie klingt das schon wieder nach einem Angebot, das Sie nicht ablehnen können.

Durch die Implementierung der dynamischen Property und der drei Methoden defaultValueForKey:, needsDisplayForKey: und drawInContext: zeigt der Layer nun auch ein Tortenstück an.


Rheinwerk Computing - Zum Seitenanfang

6.3.2Vordefinierte Layer-KlassenZur nächsten ÜberschriftZur vorigen Überschrift

Core Animation stellt auch fertige Unterklassen von CALayer für verschiedene Anwendungsfälle bereit, deren Handhabung das Beispielprojekt Layer zeigt. Die Ausgabe der beschriebenen Layer sehen Sie in Abbildung 6.18.

Abbildung

Abbildung 6.18 Ausgabe der Layer-App

Projektinformation

Den Quellcode des Beispielprojekts Layer finden Sie auf der DVD unter Code/Apps/iOS5/Layer oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS5/Layer.

Mit einem Layer der Klasse CAGradientLayer können Sie Farbverläufe darstellen, die auf zwei oder mehr Farben basieren. Die Farben übergeben Sie an den Layer als CGColorRef-Werte in einem NSArray. Der Farbverlauf verläuft entlang einer Linie, die Sie über die Propertys startPoint und endPoint festlegen.

Relative Positionsangaben bei Layern

Positionen oder Rechtecke innerhalb eines Layers müssen Sie in der Regel durch Werte zwischen 0 und 1 beschreiben und nicht durch absolute Koordinaten. Den Ankerpunkt eines Layers können Sie beispielsweise über die Property anchorPoint festlegen. Standardmäßig ist das der Mittelpunkt des Layers, und diese Property hat somit den Wert (0,5, 0,5).

Abbildung 6.19 enthält zwei Gradientenverläufe von Weiß nach Schwarz mit unterschiedlichen Start- und Endpunkten. Die Lage der Punkte verdeutlicht dabei jeweils eine Verbindungslinie auf dem Gradienten. Der linke Verlauf startet in der Ecke links oben und endet unten rechts. Der Startpunkt hat also den Wert (0, 0) und der Endpunkt (1, 1). Der rechte Gradient verläuft von oben nach unten, was sich über den Startpunkt (0,5, 0) und den Endpunkt (0,5, 1) realisieren lässt.

Abbildung

Abbildung 6.19 Gradientenverlauf zwischen Start- und Endpunkt

Das Beispielprojekt legt einen Gradienten-Layer mit drei Farben und einem Verlauf von oben links nach unten rechts folgendermaßen an:

CAGradientLayer *theLayer = [CAGradientLayer layer];
NSArray *theColors = @[(id)[UIColor redColor].CGColor,
(id)[UIColor greenColor].CGColor,
(id)[UIColor blueColor].CGColor];

self.frame = inFrame;
theLayer.colors = theColors;
theLayer.startPoint = CGPointMake(0.0, 0.0);
theLayer.endPoint = CGPointMake(1.0, 1.0);

Listing 6.42 Anlegen eines Layers mit Gradientenverlauf

Über Layer der Klasse CATextLayer können Sie Texte darstellen. Die Implementierung des Layers beruht auf Core Text. Das ist ein Low-Level-Framework für die Ausgabe von Texten. Sie können über diesen Layer auch Texte mit Hervorhebungen (z. B. fett oder kursiv) und unterschiedlichen Schriftarten ausgeben. In Listing 6.43 sehen Sie ein Beispiel, wie Sie einen Textlayer anlegen können. Dabei sorgt die Property wrapped dafür, dass der Text im Layer umbricht.

CATextLayer *theLayer = [CATextLayer layer];
CGAffineTransform theIdentity = CGAffineTransformIdentity;
CTFontRef theFont = CTFontCreateWithName(
(CFStringRef)@"Courier", 24.0, &theIdentity);

theLayer.frame = inFrame;
theLayer.font = theFont;
theLayer.fontSize = 20.0;
theLayer.backgroundColor = [UIColor whiteColor].CGColor;
theLayer.foregroundColor = [UIColor blackColor].CGColor;
theLayer.wrapped = YES;
theLayer.string = @"Die heiße Zypernsonne quälte Max und
Victoria ja böse auf dem Weg bis zur Küste.";
CFRelease(theFont);

Listing 6.43 Anlegen eines Textlayers

Die Klasse CAScrollLayer erlaubt die Anzeige von Inhalten, die größer sind als der verfügbare Bereich für die Darstellung. Sie zeigen also wie ein UIScrollView immer nur einen Ausschnitt des Inhalts an. Im Gegensatz zum View verarbeitet der Layer hingegen keine Scrollgesten, und er zeigt auch keine Scrollbalken an.

Sie können den angezeigten Ausschnitt des Scroll-Layers über die Methoden scrollToPoint: und scrollToRect: festlegen. Über scrollToPoint: legen Sie die linke obere Ecke des Ausschnitts für die Anzeige fest. Dabei geben Sie den Punkt in absoluten Koordinaten zum Layer-Inhalt an. Bei scrollToRect: übergeben Sie ein Rechteck aus dem Koordinatensystem des Inhalts. Der Scroll-Layer stellt dann einen Ausschnitt des Inhalts dar, der dieses Rechteck enthält. Listing 6.44 legt einen Scroll-Layer an, der einen Textlayer enthält.

CAScrollLayer *theLayer = [CAScrollLayer layer];
CATextLayer *theTextLayer = ...;

theLayer.frame = inFrame;
[theLayer addSublayer:theTextLayer];
theLayer.scrollMode = kCAScrollVertically;

Listing 6.44 Anlegen eines Scroll-Layers

Ein Layer der Klasse CAShapeLayer stellt einen Core-Graphics-Pfad dar. Dabei können Sie diesen Pfad über die Property path setzen. Auch für die anderen üblichen Verdächtigen gibt es entsprechende Propertys. Sie setzen beispielsweise die Füllfarbe über fillColor und die Linienfarbe über strokeColor.

CAShapeLayer *theLayer = [CAShapeLayer layer];
CGMutablePathRef thePath = CGPathCreateMutable();
CGFloat theOffset = CGRectGetHeight(inFrame) / 2.0;
CGFloat theWidth = CGRectGetWidth(inFrame);

theLayer.frame = inFrame;
theLayer.backgroundColor = [UIColor whiteColor].CGColor;
theLayer.strokeColor = [UIColor blackColor].CGColor;
theLayer.fillColor = [UIColor clearColor].CGColor;
theLayer.lineWidth = 1.0;
CGPathMoveToPoint(thePath, NULL, 0.0, f(0.0) + theOffset);
for(CGFloat x = 1.0; x < theWidth; x += 1.0) {
CGPathAddLineToPoint(thePath, NULL, x,
f(x * M_PI / theWidth) + theOffset);
}
theLayer.path = thePath;
CGPathRelease(thePath);

Listing 6.45 Erzeugung eines Shapelayers mit einem Pfad

Wie Sie in Listing 6.45 sehen, erzeugen Sie Core-Graphics-Pfade analog zu den Zeichenpfaden in einem Grafikkontext. Im Gegensatz zu den Kontextpfaden können Sie die Pfade der Typen CGPathRef und CGMutablePathRef mehrmals verwenden und auch als Werte in Ihren Objekten speichern. Auch der Grafikkontext kann mit diesen Pfaden umgehen; er unterstützt verschiedene Funktionen, mit denen Sie Pfade zeichnen, füllen oder zum Kontext hinzufügen können.


Rheinwerk Computing - Zum Seitenanfang

6.3.3Der Layer mit der MaskeZur nächsten ÜberschriftZur vorigen Überschrift

Layer können Sie außerdem zum Beschneiden des Inhalts anderer Layer verwenden. Dazu besitzt die Klasse CALayer die Property mask, der Sie einen Layer zuweisen können. Der Layer, dem Sie eine Maske zuweisen, zeigt dann nur an den Pixeln seinen Inhalt an, an denen das entsprechende Pixel in der Maske nicht transparent ist. Die Alphamaske des Masken-Layers dient also als Schablone für den Layer; alle anderen Farbwerte sind für die Darstellung irrelevant. Allerdings beachtet der Layer die Stärke der Transparenz, und Sie können somit über die Maske auch den Hintergrund durch den Layer durchscheinen lassen.

Im Beispielprojekt Layer können Sie eine Ellipse als Maske für alle Layer im Layer des Views festlegen. Der Viewcontroller erzeugt diese Maske über einen entsprechenden Shapelayer. Wie Sie in Abbildung 6.20 sehen, funktioniert das nicht nur bei den Layern, die der Viewcontroller explizit angelegt hat, sondern auch bei denen, die zu UIKit-Elementen gehören. Aus diesem Grund zeigt die App Teile des vorletzten Elements, des Sliders, nicht an. Sie können also mit Hilfe von Masken auch die Views in Ihren Apps verändern.

Abbildung

Abbildung 6.20 Verschiedene Layer mit Masken

Für die Beschreibung der Masken können Sie beliebige Layer-Klassen verwenden. Beispielsweise können Sie durch die Kombination eines Gradienten-Layers mit einem Textlayer als Maske Farbverläufe in den Buchstaben des Textes erzeugen. Abbildung 6.21 zeigt diesen Effekt anhand der Kombination der ersten beiden Layer aus Abbildung 6.18, wobei allerdings der Textlayer keinen weißen Hintergrund besitzt.

Abbildung

Abbildung 6.21 Gradienten-Layer mit Textlayer als Maske


Rheinwerk Computing - Zum Seitenanfang

6.3.4Unser Button soll schöner werdenZur nächsten ÜberschriftZur vorigen Überschrift

Die Klasse GradientButton der Games-App verwendet einen Gradienten-Layer, um optisch ansprechende Buttons darzustellen. Dazu fügt er in seiner Methode awakeFromNib einen weiteren Sublayer hinzu:

- (void)awakeFromNib {
[super awakeFromNib];
CALayer *theLayer = self.layer;
CAGradientLayer *theBackground = [CAGradientLayer layer];

theLayer.cornerRadius = 10.0;
theLayer.masksToBounds = YES;
theBackground.frame = theLayer.bounds;
theBackground.startPoint = CGPointMake(0.5, 0.2);
theBackground.endPoint = CGPointMake(0.5, 0.9);
theBackground.colors = self.normalColors;
theBackground.zPosition = –1;
[theLayer addSublayer:theBackground];
self.backgroundLayer = theBackground;
}

Listing 6.46 Hinzufügen eines Layers für den Hintergrund

Der Layer für den Hintergrund erhält die z-Position –1, damit er hinter den anderen Elementen des Buttons liegt. Über die z-Position legen Sie die Lage der Layer zueinander fest. Wenn zwei Layer sich überlappen, dann überdeckt der Layer mit der höheren z-Position den Layer mit der niedrigeren.

Da der Button einen transparenten Hintergrund hat, verdeckt er indes nicht den Hintergrund-Layer. Wenn Sie den Button drücken, verändert sich die Farbe des Verlaufs wie beim Redo-Button in Abbildung 6.22. Dazu besitzt die Klasse zwei Getter, die jeweils ein Array mit Farben liefern. Die Arrays müssen allerdings die Farbwerte als CGColorRef-Werte enthalten:

- (NSArray *)normalColors {
return [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:0.4 green:0.4 blue:1.0
alpha:1.0].CGColor,
(id)[UIColor colorWithRed:0.0 green:0.0 blue:0.6
alpha:1.0].CGColor,
(id)[UIColor colorWithRed:0.0 green:0.0 blue:0.8
alpha:1.0].CGColor, nil];
}

- (NSArray *)highligthedColors {
return [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:1.0 green:0.4 blue:0.4
alpha:1.0].CGColor,
(id)[UIColor colorWithRed:0.6 green:0.0 blue:0.0
alpha:1.0].CGColor,
(id)[UIColor colorWithRed:0.8 green:0.0 blue:0.0
alpha:1.0].CGColor, nil];
}

Listing 6.47 Definition der Verlaufsfarben für den Hintergrund

Abbildung

Abbildung 6.22 Änderung des Hintergrundverlaufs bei gedrückten Buttons

Damit der Button die Farbe beim Drücken verändert, überschreiben Sie die Methode setHighlighted:, die Cocoa Touch bei dieser Zustandsänderung aufruft.

- (void)setHighlighted:(BOOL)inHighlighted {
super.highlighted = inHighlighted;
if(inHighlighted) {
self.backgroundLayer.colors = self.highligthedColors;
}
else {
self.backgroundLayer.colors = self.normalColors;
}
}

Listing 6.48 Änderung des Farbverlaufs beim Drücken des Buttons

Das sind auch schon alle Methoden, die der Gradienten-Button benötigt. Wenn Sie einen solchen Button im Interface Builder anlegen möchten, fügen Sie dafür ein Objekt der Klasse UIButton ein, dem Sie im Attributinspektor den Typ Custom und Weiß als Textfarbe zuordnen; über den Identitätsinspektor weisen Sie ihm außerdem die Klasse GradientButton zu.


Rheinwerk Computing - Zum Seitenanfang

6.3.5Spieglein, Spieglein an der WandZur nächsten ÜberschriftZur vorigen Überschrift

Sie können auch auf den Layer-Inhalt zugreifen, beispielsweise um Screenshots zu erzeugen. Aber auch andere Möglichkeiten, wie beispielsweise Spiegelungen oder Vergrößerungen (Lupeneffekt), können Sie damit realisieren. Die beiden Spiele verwenden einen View der Klasse NumberView, um ihre Spielstände anzuzeigen. In diesem View liegen jeweils drei Views der Klasse DigitView, die jeweils die einzelnen Ziffern darstellen. Der Numberview erzeugt auch eine gespiegelte, abgeschwächte Kopie der Ziffern, so dass der Eindruck einer glänzenden Oberfläche entsteht.

Abbildung

Abbildung 6.23 Beispiel für eine Spiegelung

Für einen Spiegelungseffekt müssen Sie eine vertikal gespiegelte und gestauchte Kopie des Originalbildes erstellen. Außerdem muss sich das gespiegelte Bild mit zunehmender Entfernung vom Originalbild abschwächen. Ein Beispiel für eine Spiegelung stellt Abbildung 6.23 dar, wo Sie diese drei Effekte – spiegelverkehrte Darstellung, Stauchung und Abschwächung – sehen.

Tipp

Die Klasse NumberView spiegelt nach diesem Prinzip alle enthaltenen Views. Sie können die nachfolgend beschriebenen Methoden aus der Kategorie UIView(MirrorImage) auch in eigene Viewklassen integrieren, um Spiegelungen für beliebige Views zu erhalten.

Die erste Aufgabe, um die Spiegelung zu erzeugen, ist die Bestimmung des Layer-Inhalts. Sie müssen dazu den Inhalt in ein Bild zeichnen. Die Klasse CALayer stellt für das Zeichnen der Layer-Hierarchie die Methode renderInContext: zur Verfügung, die den Layer-Inhalt in einen beliebigen Grafikkontext zeichnet. Das Abbild eines Views wird im Beispiel über die Methode mirroredImageWithScale: in der Kategorie UIView(MirrorImage) erzeugt. Sie erstellt ein vertikal gespiegeltes und gestauchtes Abbild eines Views, wobei Sie über den Parameter den Grad der Stauchung bestimmen können. Wenn Sie hierfür negative Werte verwenden, schalten Sie die Spiegelung aus. Sie erzeugen also über den Wert -1 ein genaues Abbild des Views.

Damit das Bild auf einem Retina-Display nicht unscharf wirkt, erzeugt die Methode den Grafikkontext mit der gleichen Pixelskalierung wie der Bildschirm, wozu sie die Property scale des Hauptbildschirms ausliest. Diesen Wert können Sie für den dritten Parameter der Funktion UIGraphicsBeginImageContextWithOptions verwenden, um Bilder in der entsprechenden Auflösung zu erzeugen.

- (UIImage *)mirroredImageWithScale:(CGFloat)inScale {
CALayer *theLayer = self.layer;
CALayer *thePresentationLayer =
[theLayer presentationLayer];
CGRect theFrame = self.frame;
CGSize theSize = theFrame.size;
UIScreen *theScreen = [UIScreen mainScreen];
CGContextRef theContext;
UIImage *theImage;

if(thePresentationLayer) {
theLayer = thePresentationLayer;
}
if([theScreen respondsToSelector:@selector(scale)]) {
CGFloat theScreenScale = [theScreen scale];

UIGraphicsBeginImageContextWithOptions(
theSize, NO, theScreenScale);
}
else {
UIGraphicsBeginImageContext(theSize);
}
theContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(theContext, 1.0, -inScale);
CGContextTranslateCTM(theContext, 0.0, -theSize.height);
[theLayer renderInContext:theContext];
theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

Listing 6.49 Erzeugung eines gespiegelten Abbildes eines Views

Das doppelte Layerchen

Core Animation verwendet für die Darstellung eines Layers zwei weitere Layer. Der Präsentations-Layer enthält den aktuellen Zustand des Layers. In der Regel ist er einfach eine Kopie des Layers des Views. Während einer Animation unterscheiden sich jedoch die Werte der animierten Propertys des Präsentations-Layers von dem Original, da der Präsentations-Layer den aktuellen Animationszustand darstellt. Weil die Spiegelung auch mit Animationen funktionieren soll, verwendet Listing 6.49 den Präsentations-Layer des Layers, sofern er einen besitzt.

Da Sie jetzt ein gestauchtes und gespiegeltes Abbild eines Views erzeugen können, fehlt nur noch der Abschwächungseffekt, bei dem die Intensität der Farben des Spiegelbildes mit zunehmender Entfernung vom Originalbild nachlässt. Dieser Effekt lässt sich über einen grauen Gradientenverlauf und die Funktion CGContextClipToMask erreichen. Die Maske ist dabei ein Graustufenverlauf mit Grauwerten von 80 % bis 0 %. Der Kontext verwendet jeweils den Grauwert der Maske als Alphawert für den Pixel, so dass dunklere Grauwerte zu durchsichtigeren Pixeln führen. Der Verlauf ist ebenfalls ein Bild, das die Methode createGradientImageWithSize:gray: erzeugt; es braucht allerdings nur einen Punkt breit zu sein, da es CGContextClipToMask automatisch auf die richtige Breite streckt.

- (void)drawMirrorWithScale:(CGFloat)inScale {
CGRect theFrame = self.frame;
CGSize theSize = theFrame.size;
CGPoint thePoint = CGPointMake(
CGRectGetMinX(theFrame), CGRectGetMaxY(theFrame));
CGFloat theHeight = theSize.height * inScale;
CGImageRef theGradient = [self createGradientImageWithSize:
CGSizeMake(1.0, theHeight) gray:0.8];
CGContextRef theContext = UIGraphicsGetCurrentContext();
UIImage *theImage = [self mirroredImageWithScale:inScale];
CGRect theRect;

theRect.origin = thePoint;
theRect.size = theSize;
CGContextSaveGState(theContext);
CGContextClipToMask(theContext, theRect, theGradient);
[theImage drawAtPoint:thePoint];
CGImageRelease(theGradient);
CGContextRestoreGState(theContext);
}

Listing 6.50 Zeichnen der Spiegelung mit einer Graustufenmaske

Die Methode drawMirrorWithScale: positioniert die Maske und das Spiegelbild an der linken unteren Ecke des Views. Dabei erzeugt die Methode createGradientImageWithSize:gray: einen Graustufenverlauf vom angegebenen Wert bis 0 %. Die Implementierung beruht dabei komplett auf Core Graphics. Der Bitmapkontext in der Methode benutzt einen Graustufenfarbraum, der nur zwei Kanäle (Weiß- und Alphawert) besitzt. Aus diesem Grund muss das Array theColors mit den Farben für den Verlauf auch nur vier Werte statt acht enthalten.

- (CGImageRef)createGradientImageWithSize:(CGSize)inSize 
gray:(float)inGray {
CGImageRef theImage = NULL;
CGColorSpaceRef theColorSpace =
CGColorSpaceCreateDeviceGray();
CGContextRef theContext = CGBitmapContextCreate(
NULL, inSize.width, inSize.height, 8, 0,
theColorSpace, kCGBitmapByteOrderDefault);
CGFloat theColors[] = {inGray, 1.0, 0.0, 1.0};
CGGradientRef theGradient =
CGGradientCreateWithColorComponents(
theColorSpace, theColors, NULL, 2);

CGContextDrawLinearGradient(theContext, theGradient,
CGPointZero, CGPointMake(0, inSize.height),
kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(theColorSpace);
CGGradientRelease(theGradient);
theImage = CGBitmapContextCreateImage(theContext);
CGContextRelease(theContext);
return theImage;
}

Listing 6.51 Erzeugung des Graustufenverlaufs als Bild

Die Klasse NumberView verwendet nun die Methode drawMirrorWithScale: in ihrer Implementierung von drawRect:, um den Spiegelungseffekt zu erzeugen. Dabei erzeugt sie für jeden Subview eine eigene Spiegelung (siehe Listing 6.52) mit dem Skalierungsfaktor 0,6.

- (void)drawRect:(CGRect)inRect {
for(UIView *theView in self.subviews) {
[theView drawMirrorWithScale:0.6];
}
}

Listing 6.52 Spiegelung der Subviews

Abbildung 6.24 zeigt die Spiegelung der Klasse NumberView in der Applikation. Wie wir bereits erwähnt haben, funktioniert sie auch während der Animation der enthaltenen Layer, so dass der Effekt sehr realistisch wirkt. Für die Anzeige der Ziffern verwendet der Numberview drei Views mit der Klasse DigitView, die einen animierten Wechsel ihrer Ziffern erlauben.

Abbildung

Abbildung 6.24 Spiegelung der Ziffern des Zugzählers

Wie der Pieview besitzt auch der Numberview einen Layer mit einer eigenen Klasse, die allerdings relativ klein ist und keine eigene Header- und Implementierungsdatei besitzt; der komplette Code befindet sich stattdessen in der Datei DigitView.m. Sie stellt nur die dynamische Property digit zur Verfügung. Listing 6.53 enthält die komplette Deklaration und Implementierung:

@interface DigitLayer : CALayer

@property (nonatomic) CGFloat digit;

@end

@implementation DigitLayer

@dynamic digit;

+ (id)defaultValueForKey:(NSString *)inKey {
return [inKey isEqualToString:kDigitKey] ?
[NSNumber numberWithFloat:0.0] :
[super defaultValueForKey:inKey];
}

+ (BOOL)needsDisplayForKey:(NSString *)inKey {
return [inKey isEqualToString:kDigitKey] ||
[super needsDisplayForKey:inKey];
}

Listing 6.53 Der Layer für die Darstellung der Ziffern

Obwohl die Ziffern nur ganzzahlige Werte zwischen 0 und 10 annehmen können, speichert der Layer sie als Fließkommazahl ab. Er soll bei der Animation ja auch Zwischenzustände darstellen können, was sich über ganze Zahlen nicht realisieren lässt. Die Darstellung übernimmt der Digitview, der ja das Delegate des Layers ist. Er zeichnet dazu zwölf Ziffern (9, 0 bis 9 und noch mal die 0) untereinander, wobei er die vertikale Startposition so verschiebt, dass genau die richtige Ziffer im sichtbaren Bereich des Views zu sehen ist, was er durch ein geeignetes Clipping sicherstellt. Abbildung 6.25 stellt den Wechsel von der Ziffer 5 zur 6 dar.

Abbildung

Abbildung 6.25 Ziffernwechsel des Digitviews

Der View zeichnet den Layer-Inhalt über die Delegate-Methode drawLayer:inContext:, die Listing 6.54 enthält. Die Implementierung basiert dabei komplett auf Core Graphics und verwendet größtenteils Funktionen, die Sie bereits in Kapitel 3, »Sehen und anfassen«, kennengelernt haben.

Zur Darstellung der Texte für die Ziffern legt die Methode in Listing 6.54 zunächst den Font über die Funktion CGContextSelectFont und eine Transformationsmatrix über CGContextSetTextMatrix fest. Da Core Graphics Texte normalerweise vertikal gespiegelt darstellt, gleicht die Transformationsmatrix im Listing das wieder aus.

- (void)drawLayer:(CALayer *)inLayer 
inContext:(CGContextRef)inContext {
CGRect theBounds = self.bounds;
CGSize theSize = theBounds.size;
float theDigit = [(DigitLayer *)inLayer digit];
UIFont *theFont = self.font;
CGSize theFontSize = [@"0" sizeWithFont:theFont];
CGFloat theX = (theSize.width – theFontSize.width) / 2.0;
CGFloat theY = (theFont.capHeight – theSize.height) / 2.0;

theY -= theDigit * theSize.height;
CGContextSaveGState(inContext);
CGContextClipToRect(inContext, theBounds);
CGContextSetFillColorWithColor(inContext,
self.backgroundColor.CGColor);
CGContextFillRect(inContext, theBounds);
CGContextSetRGBFillColor(inContext, 0.0, 0.0, 0.0, 1.0);
CGContextSetRGBStrokeColor(inContext, 0.0, 0.0, 0.0, 1.0);
CGContextSelectFont(inContext, [theFont.fontName
cStringUsingEncoding:NSMacOSRomanStringEncoding],
theFont.pointSize, kCGEncodingMacRoman);
CGContextSetTextMatrix(inContext,
CGAffineTransformMakeScale(1.0, –1.0));
for(int i = 9; i <= 20; ++i) {
char theCharacter = '0' + (i % 10);

CGContextShowTextAtPoint(inContext, theX, theY,
&theCharacter, 1);
theY += theSize.height;
}
CGContextRestoreGState(inContext);
[self.superview setNeedsDisplay];
}

Listing 6.54 Zeichnen der Ziffern im Digitview

Die Variablen theX und theY enthalten jeweils die Koordinaten der Ziffern, wobei der x-Wert fest bleibt und der y-Wert sich in jedem Schleifendurchlauf um die Höhe des Views erhöht. Die Anfangswerte für diese beiden Variablen sind jeweils die Abstände zum Rand des Views bei zentrierter Positionierung des Zeichens »0« im View; außerdem zieht die Methode für jede Ziffer, die vor dem aktuellen Wert liegt, jeweils einmal die View-Höhe vom y-Wert ab.

Die etwas seltsamen Randwerte für die Schleifenvariable i kommen übrigens durch die eigentliche Zahlenfolge 9, 0, 1, ..., 9, 0 zustande, die sich relativ einfach über den Divisionsrest mit 10 berechnen lässt (siehe Kapitel 3). Sie können das leicht nachprüfen: 9 = 9 % 10, 0 = 10 % 10, 1 = 11 % 10 und so weiter.

Es geht auch einfacher

Mit der Klasse CAReplicationLayer lässt sich der Programmieraufwand für den Spiegelungseffekt reduzieren; allerdings ist damit auch der Effekt nicht ganz so schön wie mit dem bereits vorgestellten Weg, und der Replication-Layer sollte nur gleich hohe Subviews spiegeln, da der Layer die gleiche Transformationsmatrix auf alle Sublayer anwendet.

@implementation MirrorView

+ (Class)layerClass {
return [CAReplicatorLayer class];
}

- (void)awakeFromNib {
[super awakeFromNib];
CAReplicatorLayer *theLayer =
(CAReplicatorLayer *) self.layer;
UIView *theSubview = [self.subviews objectAtIndex:0];
CATransform3D theTransform = CATransform3DIdentity;
CGFloat theHeight = –0.6 *
CGRectGetHeight(theSubview.frame);
CATransform3D theScale = CATransform3DMakeScale(
1.0, –0.6, 1.0);



theLayer.instanceAlphaOffset = –0.3;
theLayer.instanceCount = 2;
theLayer.instanceTransform = CATransform3DTranslate(
theScale, 0.0, theHeight, 0.0);
}

@end

Der Datentyp CATransform3D stellt affine Transformationsmatrizen im dreidimensionalen Raum dar, deren Verwendung der von CGAffineTransform sehr ähnelt. Die Property instanceCount legt die Anzahl der Kopien fest; in diesem Fall ein Original und eine Kopie für die Spiegelung. Der Layer addiert den Wert der Property instanceAlphaOffset zu dem Alphawert der Spiegelung. Ein Beispiel für eine solche Spiegelung sehen Sie in Abbildung 6.26.

Sowohl die Erzeugung als auch die Darstellung einer Spiegelung über Replication-Layer ist also einfacher als der Weg über die Methode renderInContext:; allerdings ist das Ergebnis auch nicht ganz so schön.

Abbildung

Abbildung 6.26 Spiegelungseffekt für Arme

So, jetzt wird es aber langsam Zeit, dass Sie Ihren Layern Beine machen und sie in Bewegung setzen.


Rheinwerk Computing - Zum Seitenanfang

6.3.6Der bewegte LayerZur nächsten ÜberschriftZur vorigen Überschrift

Sie können Layer mit Animationen versehen und dadurch ihre Inhalte bewegen. Dadurch ergeben sich weitere Möglichkeiten für eine ansprechendere Oberflächengestaltung. Dabei sind Animationen Objekte, deren Klassen die Basisklasse CAAnimation haben, und Sie starten sie durch das Hinzufügen zum Animationsverzeichnis des Layers, und zwar über die Methode addAnimation:forKey:.

Wenn Sie eine Animation über eine der beiden Methoden removeAnimationForKey: oder removeAllAnimations aus dem Verzeichnis entfernen, stoppt sie sofort, und der Layer zeigt den aktuellen Zustand seiner Werte an. Nehmen wir an, die Property opacity eines Layers hat den Wert ,1 und Sie animieren diesen Wert von 1 nach 0. Wenn Sie die Animation beim Wert 0,5 aus dem Verzeichnis entfernen, springt die Darstellung sofort auf den Wert 1 um. Das passiert sogar, wenn Core Animation die Animation nach ihrem regulären Ende automatisch entfernt. Der Property-Wert von opacity ist also in diesem Beispiel nach der Animation immer 1.

Gibt es ein Leben nach der Animation?

Das automatische Entfernen lässt sich über die Property removeOnCompletion der Animation unterbinden. Wenn sie den Wert NO hat, verbleibt die Animation nach Ablauf im Animationsverzeichnis des Layers, der dann auch weiterhin den Endwert der Animation für die Property verwendet. Im Beispiel hätte der Layer in diesem Fall auch nach Ablauf der Animation den Wert 0 für opacity; allerdings nur so lange, bis Sie die Animation aus dem Verzeichnis entfernen.

In der Regel ist es besser, die Property vor der Animation auf den gewünschten Endwert zu setzen. Dadurch hat der Layer nach der Animation immer diesen Wert. Eine Animation, die nach Ihrem Ablauf im Animationsverzeichnis des Layers verbleibt, erinnert an eine gespannte Mausefalle – eine falsche Bewegung, und sie schnappt zu.

Die Property duration der Animation legt die Anzahl der Zeiteinheiten der Animation fest. Eine Zeiteinheit ist in der Regel eine Sekunde; Sie können die Dauer aber über die Property speed anpassen.

Bei einer kontinuierlichen Animation setzen Sie über die Property repeatCount die Anzahl der Wiederholungen. Die Gesamtzeit beträgt in diesem Fall repeatCount * duration Zeiteinheiten. Alternativ können Sie auch über repeatDuration die Gesamtzeit festlegen, und die Anzahl der Wiederholungen ist repeatDuration / duration. Wenn Sie die Property autoreverses auf YES setzen, ändert die Animation jeweils am Ende ihre Richtung, wodurch sich außerdem entweder die Gesamtzeit verdoppelt oder die Anzahl der Wiederholungen halbiert.

Core Animation stellt Ihnen drei grundlegende Animationstypen zur Verfügung:

  1. Property-Animationen
  2. Transitionen
  3. Animationsgruppen

Wir stellen Ihnen diese Typen im Folgenden genauer vor.

Property-Animationen

Dieser Animationstyp verändert den Wert einer Layer-Property während der Ausführung. Er basiert auf der Klasse CAPropertyAnimation, in deren Objekten Sie den Keypath auf die zu animierende Property angeben müssen. Um diesen Animationstyp anzuwenden, verwenden Sie die Unterklassen CABasicAnimation oder CAKeyframeAnimation. Alternativ können diese Animationen auch den Schlüssel aus dem Animationsverzeichnis verwenden, wenn Sie sie über den Convenience-Konstruktor animation anlegen. Dadurch können Sie mit einem Animationsobjekt mehrere unterschiedliche Property-Werte animieren. Beispielsweise lässt sich die Property opacity folgendermaßen animieren:

CABasicAnimation *theAnimation = [CABasicAnimation animation];

theLayer.opacity = 0.0;
theAnimation.fromValue = @1;
theAnimation.toValue = @0;
theAnimation.duration = 1.0;
[theLayer addAnimation:theAnimation forKey:@"opacity"];

Listing 6.55 Einfache Animation eines Property-Wertes

Abhängig von der Property können dabei die Interpolationswerte Objekte der Klassen NSNumber (für Ganzzahl- und Fließkommawerte) oder NSValue (für die Strukturen CGPoint, CGSize, CGRect und CATransform3D) sein.

Eine Animation der Klasse CABasicAnimation interpoliert zwischen zwei Property-Werten. Sie können dabei über die Propertys fromValue und toValue den Start- beziehungsweise den Endwert der Animation festlegen oder über die Property byValue den Abstand vom Start- oder Endwert angeben. Dabei sind unterschiedliche Kombinationen beim Setzen dieser Propertys möglich. Die möglichen Kombinationen sind in Tabelle 6.6 dargestellt. Dabei bedeutet ein »x« in den drei linken Spalten, dass Sie die entsprechende Property im Animationsobjekt auf einen Wert ungleich nil gesetzt haben, und die Angabe »aktueller Property-Wert« bezieht sich auf den Präsentations-Layer.

Tabelle 6.6 Animationsbereich in Abhängigkeit von den gesetzten Propertys

fromValue byValue toValue Animation
von nach

x

nil

x

fromValue

toValue

x

x

nil

fromValue

fromValue + byValue

nil

x

x

toValuebyValue

toValue

x

nil

nil

fromValue

aktueller Property-Wert

nil

x

nil

aktueller Property-Wert

aktueller Property-Wert plus byValue

nil

nil

x

aktueller Property-Wert

toValue

Das Setzen des Property-Wertes toValue in Listing 6.55 ist also nicht notwendig, da die Animation auch automatisch diesen Wert als Zielwert verwendet. Das folgende Beispiel verschiebt einen Layer zwischen zwei Punkten. Als Property-Namen verwendet die Animation den Schlüssel, unter dem sie im Animationsverzeichnis des Layers liegt – also position.

CABasicAnimation *theAnimation = [CABasicAnimation animation];
theAnimation.fromValue =
[NSValue valueWithCGPoint:CGPointMake(10.0, 10.0)];
theAnimation.toValue =
[NSValue valueWithCGPoint:CGPointMake(100.0, 10.0)];
[theLayer addAnimation:theAnimation forKey:@"position"];

Listing 6.56 Verschieben eines Layers über eine Animation

Über die Klasse CAKeyframeAnimation können Sie Property-Animationen mit komplexeren Verläufen erzeugen. Über die Property values übergeben Sie dabei ein Array mit den Werten für die Zwischenzustände der Animation. Falls Sie mit der Animation Werte vom Typ CGPoint animieren wollen, können Sie auch die Property path verwenden. Sie erwartet einen Core-Graphics-Pfad. Damit ist es beispielsweise einfach, einen Layer entlang einer Kreislinie zu bewegen:

CAKeyframeAnimation *theAnimation =  
[CAKeyframeAnimation animation];
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathAddEllipseInRect(thePath, NULL,
CGRectMake(140.0, 150.0, 20.0, 20.0));
theAnimation.path = thePath;
theAnimation.repeatCount = 3;
theAnimation.autoreverses = YES;
theAnimation.duration = 0.5;
[theLayer addAnimation:theAnimation forKey:@"position"];
CGPathRelease(thePath);

Listing 6.57 Layer-Bewegung entlang eines Pfades

Mit Keyframe-Animationen können Sie recht ungewöhnliche Animationen verwirklichen. Abschnitt 6.3.7, »Daumenkino«, stellt dafür noch ein komplexeres Beispiel vor.

Transitionen

Der zweite Animationstyp sind Transitionen, die Core Animation über die Klasse CATransition abbildet. Diese Klasse unterstützt insgesamt vier unterschiedliche Transitionstypen, die Tabelle 6.7 auflistet. Außer in der Fade-Animation können Sie eine Richtung in der Property subtype angeben; die Konstanten kCATransitionFromRight, -Left, -Top und -Bottom enthalten die erlaubten Werte hierfür.

Tabelle 6.7 Die Transitionstypen von Core Animation

Name Beschreibung

kCATransitionFade

Blendet den bestehenden Layer aus und den neuen Layer ein.

kCATransitionMoveIn

Bewegt den neuen Layer über den bestehenden.

kCATransitionPush

Schiebt den neuen Layer herein und den bestehenden gleichzeitig hinaus.

kCATransitionReveal

Schiebt den bestehenden Layer hinaus und gibt dadurch den neuen View frei.

Sie wenden eine Transition an, indem Sie sie in das Animationsverzeichnis desjenigen Layers einfügen, der die Transitionslayer enthält. Außerdem müssen Sie einen bestehenden Layer unsichtbar machen, indem Sie ihn verstecken oder aus der Layer-Hierarchie entfernen. Analog müssen Sie den neuen Layer anzeigen oder in die Hierarchie einfügen. Listing 6.58 zeigt ein einfaches Beispiel für diesen Animationstyp.

CATransition *theTransition = [CATransition animation];
theTransition.type = kCATransitionPush;
theTransition.subtype = kCATransitionFromTop;
[[theLayer.sublayers objectAtIndex:0] setHidden:YES];
[[theLayer.sublayers objectAtIndex:1] setHidden:NO];
[theLayer addAnimation: theTransition forKey:@"transition"];

Listing 6.58 Ausführen einer Transition

Die Transitionen in Core Animation sind also den Transitionen des UIKits sehr ähnlich. Allerdings bietet Letzteres erstaunlicherweise eine größere Vielfalt an Standardübergängen.

Animationsgruppen

Die Klasse CAAnimationGroup fasst mehrere Layer-Animationen zu einer Gruppe zusammen, die Sie in einem Array an die Property animations übergeben. Die Gruppe gibt die maximale Dauer der Animationen vor und beendet die Ausführung längerer Animationen gegebenenfalls vorzeitig. Eine Animationsgruppe ist also die Queen unter den Animationen. Wenn sie aufhört zu essen, dann müssen auch alle anderen den Dessertlöffel fallen lassen. Hat beispielsweise die Gruppe eine Dauer von einer Sekunde und enthält sie eine 4 Sekunden dauernde Animation, dann wird diese Animation nur zu einem Viertel ausgeführt. In Listing 6.59 führt diese Konfiguration beispielsweise dazu, dass die Animation des Eckradius nur von 0 bis 40 anstatt bis 160 läuft.

CABasicAnimation *theAnimation =
[CABasicAnimation animationWithKeyPath:@"cornerRadius"];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theAnimation.toValue = [NSNumber numberWithFloat:160.0];
theAnimation.duration = 4.0;
theGroup.duration = 1.0;
theGroup.animations = [NSArray arrayWithObject:theAnimation];
theGroup.repeatCount = 3.0;
[theLayer addAnimation:theGroup forKey:@"group"];

Listing 6.59 Erzeugung einer Animationsgruppe


Rheinwerk Computing - Zum Seitenanfang

6.3.7DaumenkinoZur nächsten ÜberschriftZur vorigen Überschrift

Nachdem wir in Abschnitt 6.3.5, »Spieglein, Spieglein an der Wand«, die verschiedenen Animationsarten kurz vorgestellt haben, soll jetzt ein etwas umfangreicheres Beispiel für eine Keyframe-Animation folgen. In Listing 6.57 haben Sie bereits ein Beispiel für eine Bewegung entlang eines Pfades kennengelernt. Es ist jedoch auch möglich, mit einer Keyframe-Animation die Objekte einer Property des Layers auszutauschen. Sie können diese Möglichkeit beispielsweise dafür nutzen, Bildfolgen ablaufen zu lassen und somit einfache Filme wie bei einem Daumenkino abzuspielen.

Projektinformation

Den Quellcode des Beispielprojekts FlipBookAnimation finden Sie auf der DVD unter Code/Apps/iOS5/FlipBookAnimation oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS5/FlipBookAnimation.

Das Beispielprojekt FlipBookAnimation lädt eine Bildfolge aus einem GIF-Bild und stellt sie über eine Keyframe-Animation dar. Dabei öffnet und lädt die Applikation die Bildfolge über das ImageIO-Framework in der Methode readAnimationImages der Klasse FlipBookViewController, die Sie in Listing 6.60 sehen.

- (NSArray *)readAnimationImages {
NSBundle *theBundle = [NSBundle mainBundle];
NSURL *theURL = [theBundle URLForResource:
@"animated-image" withExtension:@"gif"];
NSDictionary *theOptions = [NSDictionary
dictionaryWithObjectsAndKeys:nil];
CGImageSourceRef theSource = CGImageSourceCreateWithURL(
(__bridge CFURLRef)theURL,
(__bridge CFDictionaryRef)theOptions);
size_t theCount = CGImageSourceGetCount(theSource);
NSMutableArray *theResult = [NSMutableArray
arrayWithCapacity:theCount];

for(size_t i = 0; i < theCount; ++i) {
CGImageRef theImage = CGImageSourceCreateImageAtIndex(
theSource, i,
(__bridge CFDictionaryRef)theOptions);

[theResult addObject:(__bridge_transfer id)theImage];
}
CFRelease(theSource);
return theResult;
}

Listing 6.60 Laden der Bildfolge für die Animation

Core Graphics und Objective-C

Core Foundation enthält viele Datentypen, deren zugehörige Funktionen sich analog zu den Methoden einer entsprechenden Foundation-Klasse verhalten.

Ein Beispiel dafür sind die Datentypen CFURLRef und CFDictionaryRef mit den analogen Klassen NSURL und NSDictionary.

Tatsächlich implementiert das Foundation-Framework die Klasse NSURL über den Datentyp CFURLRef, so dass Sie NSURL-Objekte auf CFURLRef und umgekehrt casten können, wie das die vierte Anweisung in Listing 6.60 macht. Diese Technologie nennt Apple Toll-free Bridging.

Wenn Sie allerdings Foundation- auf Core-Foundation-Referenzen in einer ARC-Umgebung casten, müssen Sie die Modifizierer __bridge, __bridge_retained oder __bridge_transfer verwenden, damit der ARC-Compiler weiß, wie er mit der Eigentümerschaft auf den Referenzen umgehen soll.

Wenn Sie den Modifizierer weglassen, warnt Xcode Sie an der betreffenden Stelle und macht einen Vorschlag, wie Sie die Warnung vermeiden können. In der Regel sollten Sie diesen Vorschlag befolgen. Abschnitt 6.5, »Über diese Brücke musst du gehen«, geht noch genauer auf die Toll-free Bridge und die Modifizierer ein.

Die Methode öffnet über die URL das GIF-Bild und liefert eine Referenz Typs CGImageSourceRef darauf. Danach ermittelt sie die Anzahl der Einzelbilder und schreibt diese Zahl in die Variable theCount. Über eine Schleife liest die Methode die einzelnen Bilder aus der Imagesource in ein Array. Die gelesenen Bilder sind allerdings keine UIImages, sondern haben den Typ CGImageRef, was sehr praktisch ist, da ja die contents-Property des Layers diesen Typ erwartet.

Die Methode viewDidAppear: des Viewcontrollers erzeugt und startet die Animation. Dazu verwendet sie eine Keyframe-Animation und übergibt das Array mit den Bildern an die Property values der Animation. Die Dauer eines kompletten Durchlaufs ist eine Sekunde, und die Property repeatCount bekommt den Wert HUGE_VALF zugewiesen, so dass die Animation unbegrenzt läuft.

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
NSArray *theImages = [self readAnimationImages];
CALayer *theLayer = self.animationView.layer;
CAKeyframeAnimation *theAnimation =
[CAKeyframeAnimation animation];

theAnimation.values = theImages;
theAnimation.repeatCount = HUGE_VALF;
theAnimation.duration = 1.0;
[theLayer addAnimation:theAnimation forKey:@"contents"];
}

Listing 6.61 Ausführen der Bildfolge über eine Keyframe-Animation

Die Animation stellt einen Atomkern dar, um den drei Elektronen in unterschiedlichen Bahnen kreisen. Wenn Sie die Animation langsamer laufen lassen, beispielsweise mit einer Dauer von fünf Sekunden, dann flackern die Elektronen bei der Bewegung, und Sie sehen für jedes Elektron jeweils zwei blasse Punkte (siehe Abbildung 6.27). Das liegt daran, dass die Animation die Bilder nicht wechselt, sondern aus- beziehungsweise einblendet. Diese Überblendzeiten sind dabei so lang, dass Sie die Punkte doppelt sehen. Das Beispielprojekt zeigt außerdem einen Pause-Button an, mit dem Sie die Animation unterbrechen können. Wenn Sie ihn drücken, können Sie sich diesen Dopplungseffekt in Ruhe ansehen.

Über die Property calculationMode der Klasse CAKeyframeAnimation können Sie das Überblendverhalten ändern. Sie hat standardmäßig den Wert kCAAnimationLinear, blendet also das neue Bild in der gleichen, gleichmäßigen Geschwindigkeit ein, wie sie das bestehende ausblendet. Wenn Sie stattdessen den Wert auf kCAAnimationDiscrete setzen, tauscht die Animation die Bilder ohne Überblendung aus. Dadurch sehen Sie jedes Elektron sowohl während der Bewegung als auch in den Pausen nur einmal, wenn Sie die Anweisung

theAnimation.calculationMode = kCAAnimationDiscrete;

an geeigneter Stelle in die Methode viewDidAppear: einfügen.

Abbildung

Abbildung 6.27 Die Animation macht mal eine Pause.


Rheinwerk Computing - Zum Seitenanfang

6.3.8RelativitätstheorieZur nächsten ÜberschriftZur vorigen Überschrift

In Abschnitt 6.3.6, »Der bewegte Layer«, haben Sie die Propertys duration, speed, repeatCount und autoreverses kennengelernt, mit denen Sie das Ablaufverhalten der Animation steuern. Allerdings deklariert nicht die Klasse CAAnimation diese Propertys, sondern das Protokoll CAMediaTiming. Der Versuch, die Eigenschaften einer laufenden Animation zu ändern, scheitert jedoch mit folgender Ausnahme:

Terminating app due to uncaught exception 'CAAnimationImmutable', reason: 'attempting to modify read-only animation <CAKeyframeAnimation: 0x7546810>'.

Sie können also beispielsweise eine Animation nicht einfach über ihre Property speed verlangsamen oder gar pausieren lassen.

Allerdings implementiert auch die Klasse CALayer das Protokoll, und so können Sie die Eigenschaften auch bei den Layern setzen und damit den Zeitraum beziehungsweise die Zeitachse der enthaltenen Animationen verbiegen. Wenn Sie beispielsweise die Geschwindigkeit des Layers auf 2 setzen, laufen seine Animationen doppelt so schnell ab, und wenn Sie sie auf 0 setzen, hält die Animation an.

Geschachtelte Zeiträume

Jeder Layer legt also seine eigene Zeitachse fest, die allerdings nicht nur für die enthaltenen Animationen, sondern auch für alle enthaltenen Layer gilt. Der innere Layer liegt also auch immer im Zeitraum seines Superlayers. Wenn der äußere Layer beispielsweise die Geschwindigkeit 3, der innere die halbe Geschwindigkeit und eine Animation des inneren Layers den Wert 4 hat, dann ist die Gesamtgeschwindigkeit 3 × 0,5 × 4 = 6.

Sie können also über den Layer eine Animation pausieren lassen. Um diese Funktionalität unabhängig von der Layer-Klasse zu machen, verwendet das Beispielprojekt dafür die Kategorie CALayer(AnimationPausing) mit den Methoden pause, resume und isPausing. Wenn Sie nun allerdings diese drei Methoden folgendermaßen implementieren

- (void)pause {
self.speed = 0.0;
}
- (void)resume {
self.speed = 1.0;
}
- (BOOL)isPausing {
return self.speed == 0.0;
}

Listing 6.62 Naive Implementierung der Pause-Funktionalität

stellen Sie zwei unerwünschte Effekte fest: Einerseits verschwindet der Layer-Inhalt, andererseits scheint die Animation im Hintergrund weiterzulaufen. Denn nach dem Ende der Pause befinden sich die Elektronen meistens nicht an der gleichen Position wie zu Beginn der Pause.

Vermutlich zeigt der Layer immer das Bild an, das er vor dem Beginn der Animation hatte, und das war keins, weil die App der contents-Property keinen Wert zugewiesen hat. Sie können das leicht überprüfen, indem Sie diesen Wert durch die Anweisung theLayer.contents = [theImages objectAtIndex:0]; in der Methode viewWillAppear: setzen. Durch diese Änderung zeigt nun der Layer immer das gleiche Bild in der Pause an.

Diese Änderung löst zwar nicht das Problem, jedoch wissen Sie nun, was die Ursache des Fehlers ist. Durch die Änderung der Geschwindigkeit fällt die Zeitachse des Layers auf einen Punkt – den Startzeitpunkt – zusammen, und somit bleibt auch die Animation am Startzeitpunkt stehen.

Sie müssen also die Animation so anhalten, dass sie stattdessen am aktuellen Zeitpunkt hält. Dazu liefert Ihnen die Funktion CACurrentMediaTime die aktuelle Systemzeit für die Animationen, die Sie über die Methode convertTime:fromLayer: in die aktuelle Zeit des Layers umrechnen können. Da Sie hier mit der absoluten Zeit rechnen, geben Sie für den zweiten Parameter den Wert nil an.

Den so errechneten Wert setzen Sie über die Property timeOffset des Layers. Sie enthält eine Zeitspanne, die Core Animation zu der aktuellen Zeit der Animation hinzuzählt; diese Property verschiebt also den aktuellen Punkt in der Animation. Im Gegensatz dazu legen Sie über die Property beginTime die Startzeit fest.

Sie können sich das so vorstellen: Sie stehen in Ihrer Küche und möchten plötzlich ein bestimmtes Musikstück hören. Also gehen Sie ins Wohnzimmer, suchen die Schallplatte heraus, legen sie auf den Plattenspieler und setzen die Nadel genau auf den Beginn des zweiten Stückes auf der Platte. Dann ist die Zeit vom Start in der Küche bis zum Aufsetzen der Nadel die Begintime, und die Dauer des ersten Stückes auf der Platte ist der Timeoffset, wenn die gesamte Musik auf der Platte der Animation entspricht.

Abbildung 6.28 stellt diesen Zusammenhang grafisch dar. Die Animation startet an dem Zeitpunkt, auf den die Spitze des Pfeils beginTime zeigt. Allerdings startet sie nicht am Anfang, sondern um den Timeoffset nach links verschoben. Der Animationsablauf entspricht also der dicken Linie zwischen den schwarzen Punkten.

Abbildung

Abbildung 6.28 Startzeitpunkt auf der Zeitachse

Der Timeoffset bietet also genau die Möglichkeit, die richtige Position im Animationsablauf auszuwählen, und Sie ändern die Methode pause wie folgt:

- (void)pause {
CFTimeInterval theCurrentTime = CACurrentMediaTime();
CFTimeInterval theTime =
[self convertTime:theCurrentTime fromLayer:nil];

self.speed = 0.0;
self.timeOffset = theTime;
}

Listing 6.63 Anhalten zum richtigen Animationszeitpunkt ...

Mit der Änderung aus Listing 6.63 zeigt der Layer immer schön den Animationszustand an, an dem Sie Pause gedrückt haben. Allerdings lässt das Fortsetzen der Animation noch zu wünschen übrig, da hierbei unschöne Elektronensprünge auftreten, worüber sich allenfalls Quantenphysiker freuen. Sie müssen also beim Wiederanlaufen dafür sorgen, dass der Animationszustand genau dem Zustand in der Pause entspricht. Diese Berechnung ist etwas komplizierter. Abbildung 6.29 stellt das Vorgehen dafür zunächst grafisch dar.

Abbildung

Abbildung 6.29 Zeitverschiebung beim Fortsetzen der Animation

Die Darstellung enthält zwei Zeitverläufe. Die obere Zeitachse enthält den realen Animationsverlauf: Die Animation startet und läuft eine gewisse Zeit. Dann halten Sie sie an, bis Sie über resume die Bewegung fortsetzen. Wenn Sie das Zeitstück der Pause nun mit dem abgelaufenen Animationsteil vertauschen, erhalten Sie die untere Zeitachse aus der Abbildung, und die beiden Animationsteile knüpfen nahtlos aneinander an. Auf diese Weise lässt sich also der Sprung vermeiden.

Im Code verschieben Sie das Zeitstück der Pause, indem Sie es als Begintime der Animation setzen (siehe Abbildung 6.28), und die Länge der Pause entspricht der Differenz aus der aktuellen Zeit und der Startzeit der Pause.

Listing 6.64 enthält den kompletten Code für die resume-Methode. Da die Methode convertTime:fromLayer: für ihre Berechnungen auch den Wert der Property beginTime verwendet, ist es übrigens wichtig, diesen Wert vorher auf null zu setzen.

- (void)resume {
CFTimeInterval theTime = self.timeOffset;

self.speed = 1.0;
self.timeOffset = 0.0;
self.beginTime = 0.0;
theTime = [self convertTime:CACurrentMediaTime()
fromLayer:nil] – theTime;
self.beginTime = theTime;
}

Listing 6.64 ... und Fortfahren mit dem richtigen Animationszustand


Rheinwerk Computing - Zum Seitenanfang

6.3.9Der View, der Layer, seine Animation und ihr LiebhaberZur nächsten ÜberschriftZur vorigen Überschrift

Mit einer Property-Animation können Sie allerdings nicht nur die Standard-Propertys der Layer, sondern auch beliebige eigene Propertys animieren. Dazu sollten Sie diese Propertys analog zu den Propertys part und digit in den Klassen PieLayer beziehungsweise DigitView anlegen, wie das Abschnitt 6.3.1, »Layer«, beschreibt. Hier sind noch einmal kurz die Punkte, auf die Sie bei animierbaren Propertys achten sollten:

  1. Deklarieren Sie die Property in Ihrem Layer, und implementieren Sie sie über das Schlüsselwort @dynamic. Core Animation durchsucht nur die Propertys, jedoch keine Methoden der Layer-Klasse nach animierbaren Propertys.
  2. Legen Sie über die Klassenmethode defaultValueForKey: einen Initialwert für Ihre Property fest.
  3. Außerdem müssen Sie die Klassenmethode needsDisplayForKey: überschreiben, damit Core Animation den Layer bei einer Änderung des Property-Wertes auch zeichnet.

Sie können also beispielsweise eine Animation der Klasse CABasicAnimation mit dem Schlüssel part in das Animationsverzeichnis eines Pie-Layers einfügen. Core Animation erzeugt dann während der Animation die entsprechenden Zwischenwerte für diese Property, so dass ein flüssiger Bewegungsablauf entsteht.

Bist du flüssig?

Wie Sie vielleicht schon in Abschnitt 6.3.8, »Relativitätstheorie«, bemerkt haben, sind die Animationen in Core Animation zeit- und nicht framebasiert. Das heißt, dass die von Ihnen vorgegebene Zeit für die Animation wichtiger ist als die Anzahl der erzeugten Einzelbilder (Frames). Core Animation berechnet, sofern das möglich ist, für die Animation so viele Einzelbilder, dass die Animation die vorgegebene Zeit nicht überschreitet und dabei trotzdem möglichst flüssig abläuft. Dahinter steckt sicherlich jede Menge Hirnschmalz. Wenn Sie also für Ihre Views und Layer Animationen brauchen, sollten Sie dafür Core Animation verwenden.

Mit den Methoden aus Abschnitt 6.2.1, »Animationen mit Blöcken«, ist es wesentlich einfacher, eine Animation zu erzeugen. Natürlich verwendet Cocoa Touch auch dafür Layer-Animationen, die der Layer bei Änderungen der entsprechenden Property automatisch erzeugt. Dafür wäre es naheliegend, die Erzeugung der Animation in den Setter der Property des Views oder des Layers zu verschieben. Allerdings würden Sie damit die Animationserzeugung fest mit dem View oder dem Layer verdrahten.

Core Animation stellt stattdessen für die Animationserzeugung einen eleganteren Weg zur Verfügung. Ein Layer kann bei der Änderung einer Property oder der Veränderung der Layer-Hierarchie eine Aktion auslösen. Eine Aktion ist dabei ein Objekt, dessen Klasse das Protokoll CAAction implementiert. Die Property-Änderung besteht dabei aus drei Schritten:

  1. Zuerst übernimmt der Layer den neuen Wert.
  2. Als Nächstes erzeugt er die Aktion.
  3. Danach führt der Layer die Aktion aus.

Die Layer-Methode actionForKey: erzeugt die Aktionen des Layers. Wenn Sie die Standardaktionen eines Layers verändern oder neue Aktionen hinzufügen wollen, haben Sie mehrere Möglichkeiten:

  1. Implementieren Sie die Methode actionForLayer:forKey: im Delegate des Layers.
  2. Setzen Sie über die Property actions im Layer ein Verzeichnis mit Aktionen.
  3. Der Layer durchsucht außerdem sein Style-Verzeichnis rekursiv nach Aktionen.
  4. Implementieren Sie die Klassenmethode defaultActionForKey:.

Die Methode actionForKey: sucht in der angegebenen Reihenfolge nach den Aktionen. Wenn ein Suchweg dabei nil liefert, sucht der Layer weiter. Ein Aktionsobjekt oder das Nullobjekt [NSNull null] bricht die Suche sofort ab. Dabei bedeutet die Rückgabe des Nullobjekts, dass der Layer keine Aktion zum angegebenen Schlüssel hat.

Das Protokoll CAAction besitzt nur eine Methode runActionForKey:object:arguments:, die der Layer im dritten Schritt aufruft. Dabei ist der Parameter object der Layer, der die Aktion ausgelöst hat, und der Parameter arguments ist in der Regel nil. Die Klasse CAAnimation implementiert das CAAction-Protokoll. Die Implementierung der Methode fügt dabei lediglich das Animationsobjekt in das Animationsverzeichnis des Layers ein. Die Implementierung sieht dafür ungefähr so aus:

- (void)runActionForKey:(NSString *)inKey object:(id)inLayer  
arguments:(NSDictionary *)inArguments {
[(CALayer *)inLayer addAnimation:self forKey:inKey];
}

Listing 6.65 Action-Methode einer Animation

Wenn Sie also eigene Layer-Propertys automatisch animieren möchten, brauchen Sie nur über einen der oben beschriebenen Suchwege ein Animationsobjekt zu erzeugen. Alternativ überschreiben Sie in Ihrer Layer-Klasse die Methode actionForKey:. Eine Implementierung der Delegate-Methode sieht beispielsweise so aus:

- (id<CAAction>)actionForLayer:(CALayer *)inLayer 
forKey:(NSString *)inKey {
if([kPartKey isEqualToString:inKey]) {
CABasicAnimation *theAnimation =
[CABasicAnimation animationForKeyPath:inKey];

theAnimation.fromValue =
[inLayer valueForKey:kPartKey];
return theAnimation;
}
else {
return [super actionForLayer:inLayer forKey:inKey];
}
}

Listing 6.66 Animationserzeugung für eine Layer-Property

Mit der Implementierung aus Listing 6.66 führt jede Änderung der Property part der Klasse PieView in einem Animationsblock zu einer animierten Veränderung der Grafik. Da der Layer die Aktion vor der Übernahme des Property-Wertes anlegt, liefert der Aufruf von valueForKey: noch den alten Wert. Weil der Layer dagegen den Wert vor der Ausführung der Aktion übernimmt, reicht es nach Tabelle 6.6 aus, die Property fromValue zu setzen.

Das klingt ja schon mal ganz gut, hat jedoch noch einen kleinen Schönheitsfehler: Wenn Sie die Dauer des Animationsblocks verändern, ist das der Animation für die Property part völlig schnuppe. Leider hat Apple den Zugriff auf diesen und die anderen Animationsparameter sehr gut versteckt, so dass Sie diese Werte nicht einfach auslesen können.

Die Lösung dieses Problems ist allerdings einfach und erfolgt nach dem Motto: »Klauen wir gleich die ganze Animation.« Weniger cineastisch ausgedrückt heißt das, dass die Methode also nicht die Animation selbst erzeugt, sondern dass sie sie für eine Standard-Property, in diesem Fall opacity, erzeugen lässt und sie danach anpasst.

if([kPartKey isEqualToString:inKey]) {
CABasicAnimation *theAnimation =
(id)[inLayer actionForKey:@"opacity"];

theAnimation.keyPath
= inKey;
theAnimation.fromValue =
[inLayer valueForKey:kPartKey];
theAnimation.toValue = nil;
theAnimation.byValue = nil;
}

Listing 6.67 Erzeugung der Animation über eine Standard-Property

Listing 6.67 gibt den geänderten if-Block gegenüber Listing 6.66 wieder, der die Animation über die Animation für die Property opacity erzeugt. Sie müssen in dieser Animation natürlich die Werte der Propertys keyPath und fromValue anpassen. Da Vorsicht bekanntlich die Mutter der Porzellankiste ist, sollten Sie die Property-Werte für toValue und byValue löschen. Sie können damit jetzt auch die Property part der Klasse PieView in einem Animationsblock setzen, um die Änderung zu animieren:

[UIView animateWithDuration:0.75 animations:^{
self.pieView.part = 0.6;
}];

Listing 6.68 Animation einer selbstdefinierten Property

Die Klasse NumberView im Beispielprojekt Games animiert die drei Anzeigen für die Ziffern auch über View-Animationen. Sie besitzt zwei Methoden, um einen neuen Wert zu setzen. Da ist einerseits der Setter setValue:, der nur den Wert setzt, und andererseits setValue:animated:, der außerdem eine animierte Aktualisierung der Anzeige erlaubt. Dabei basiert die zweite Methode auf der Implementierung der ersten; abhängig vom zweiten Parameter kapselt sie die Zuweisung einfach in einen Animationsblock und ähnelt damit in der Signatur vielen Methoden des UIKits:

- (void)setValue:(NSUInteger)inValue 
animated:(BOOL)inAnimated {
if(inAnimated) {
[UIView animateWithDuration:0.75 animations:^{
self.value = inValue;
}];
}
else {
self.value = inValue;
}
}

Listing 6.69 Aktualisierung des Spielstands mit und ohne Animation

Der Setter teilt den Wert für die einzelnen Ziffern der enthaltenen Digitviews auf und aktualisiert deren Werte. Dabei liegen diese Views allerdings in umgekehrter Reihenfolge im Numberview. Der erste Subview liegt also am weitesten rechts und enthält die Einerstelle, der zweite Subview liegt links daneben und enthält die Zehner und so weiter. Die Berechnung der Ziffern erfolgt dabei über eine Division durch 10 mit Rest.

- (void)setValue:(NSUInteger)inValue {
NSUInteger theValue = inValue;

for(DigitView *theView in self.subviews) {
theView.digit = value % 10;
theValue /= 10;
}
value = inValue;
}

Listing 6.70 Aktualisierung der Ziffern

Mit einer entsprechend adaptierten Version der oben beschriebenen Methode actionForLayer:forKey: erhalten Sie einen animierten Wechsel der Ziffern. Allerdings läuft der View alle Ziffern bei einem Wechsel von 9 auf 0 durch, der beispielsweise beim Übergang von der 9 auf die 10 auftritt. Um diesen Darstellungsfehler zu vermeiden, berechnet der Digitview im Setter der Property value einen besseren Wert für die Property fromValue der Animation. Der Digitview speichert diesen Wert in einer eigenen Property und übergibt ihn an die Animation. Der Wert ist 10 für den Übergang von 0 auf 9 und –1 für den Übergang von 9 auf 0.

- (void)setDigit:(NSUInteger)inDigit {
NSInteger theOldDigit = self.digit;
NSInteger theNewDigit = inDigit % 10;

if(theOldDigit == 9 && theNewDigit == 0) {
theOldDigit = –1;
}
else if(theOldDigit == 0 && theNewDigit == 9) {
theOldDigit = 10;
}
self.fromValue = @(theOldDigit);
[(DigitLayer *)self.layer setDigit:theNewDigit];
}

Listing 6.71 Berechnung eines geeigneteren Startwertes für die Animation

Diesen berechneten Startwert in fromValue übergibt die Methode actionForLayer:forKey: an die Property fromValue der Animation. Listing 6.72 zeigt die komplette Implementierung und hebt die angepasste Wertübergabe hervor.

- (id<CAAction>)actionForLayer:(CALayer *)inLayer 
forKey:(NSString *)inKey {
if([kDigitKey isEqualToString:inKey]) {
CABasicAnimation *theAnimation =
(id)[inLayer actionForKey:@"opacity"];

theAnimation.keyPath = inKey;
theAnimation.fromValue = self.fromValue;
theAnimation.toValue = nil;
theAnimation.byValue = nil;
return theAnimation;
}
else {
return [super actionForLayer:inLayer forKey:inKey];
}
}

Listing 6.72 Erzeugung der Animation für den Digitview


Rheinwerk Computing - Zum Seitenanfang

6.3.10TransaktionenZur nächsten ÜberschriftZur vorigen Überschrift

Die Methoden zur Aktionserzeugung des vorangegangenen Abschnitts bedienten sich eines kleinen, vielleicht nicht ganz sauberen Tricks, um die Animation von außen parametrisieren zu können. Das ist leider notwendig, wenn Sie die Animation über die bekannten Animationsmethoden der Klasse UIView erzeugen wollen. Core Animation bietet hingegen über sein Transaktionskonzept einen eigenen Weg, Animationen zu konfigurieren.

Jede Änderung des Layer-Baums, wie beispielsweise das Hinzufügen, Umordnen oder Entfernen von Layern oder das Ändern von Layer-Propertys, erfolgt in einer Core-Animation-Transaktion. Bislang haben Sie nur implizite Transaktionen verwendet: Wenn beispielsweise das Programm die Property part des Pie-Layers auf 0,5 gesetzt hat, hat Core Animation eine Transaktion gestartet, den neuen Wert übernommen und die Transaktion beendet.

Sie können jedoch über die Klassenmethoden begin und commit der Klasse CATransaction auch explizite Transaktionen starten und beenden. Innerhalb einer expliziten Transaktion können Sie dann über weitere Klassenmethoden von CATransaction Transaktionsparameter setzen, mit denen sich die Animationserzeugung beeinflussen lässt.

In Listing 6.73 finden Sie ein Beispiel für eine explizite Transaktion in Core Animation, die die Animationsdauer und den Animationsverlauf ändert. Innerhalb einer Transaktion müssen Sie immer erst die Transaktionsparameter ändern und danach die Änderungen an den Layern durchführen, damit die Transaktionsparameter auch bei der Animationserzeugung berücksichtigt werden. Der Animationsverlauf ist dabei ein Objekt der Klasse CAMediaTiming, das eine Bézierkurve enthält, die die Punkte (0, 0) und (1, 1) miteinander verbindet. Diese Kurve beschreibt die relative Geschwindigkeit der Animation in Beziehung zur relativen Animationszeit.

[CATransaction begin];
[CATransaction setAnimationDuration:0.75];
[CATransaction setAnimationTimingFunction:
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionLinear];
theLayer.position = CGPointMake(100.0, 200.0);
[CATransaction commit];

Listing 6.73 Explizite Transaktion in Core Animation

Abbildung 6.30 stellt die Standardanimationsverläufe von Core Animation dar: 1 kCAMediaTimingFunctionLinear, 2 kCAMediaTimingFunctionEaseIn, 3 kCAMediaTimingFunctionEaseOut, 4 kCAMediaTimingFunctionEaseInEaseOut und 5 kCAMediaTimingFunctionDefault. Sie können also beispielsweise durch den Verlauf kCAMediaTimingFunctionEaseInEaseOut erreichen, dass die Animation langsam an- und ausläuft.

Abbildung

Abbildung 6.30 Die verschiedenen Standardanimationsverläufe

Bei der Animationserzeugung müssen Sie die Transaktionsparameter allerdings explizit übernehmen und beispielsweise Listing 6.66 folgendermaßen anpassen:

CABasicAnimation *theAnimation = 
[CABasicAnimation animationForKeyPath:inKey];

theAnimation.duration = [CATransaction animationDuration];
theAnimation.timingFunction =

[CATransaction animationTimingFunction];
theAnimation.fromValue = [inLayer valueForKey:kPartKey];

Listing 6.74 Übernahme der Transaktionsparameter

Transaktionen erlauben Ihnen über die Klassenmethoden valueForKey: und setValue:forKey: auch die Übergabe beliebiger Parameter, und Sie können über setCompletionBlock: auch einen Block festlegen, den Core Animation nach der Beendigung aller Animationen ausführt, die Ihr Programm innerhalb der Transaktion erzeugt hat.

Eine weitere praktische Eigenschaft von Transaktionen ist die Möglichkeit, sie zu schachteln. Dadurch können Sie die Transaktionsparameter für bestimmte Layer-Änderungen anpassen. In Listing 6.75 sehen Sie ein einfaches Beispiel für verschachtelte Transaktionen, bei dem die Positionsänderung in einer Dreiviertelsekunde und die Änderung der Transparenz in einer halben Sekunde erfolgt.

[CATransaction begin];
[CATransaction setAnimationDuration:0.75];
theLayer.position = CGPointMake(100.0, 200.0);
[CATransaction begin];
[CATransaction setAnimationDuration:0.5];
theLayer.opacity = 0.5;
[CATransaction commit];
[CATransaction commit];

Listing 6.75 Verschachtelte Transaktionen

Dieses Beispiel ist zugegebenermaßen nicht unbedingt sinnvoll, da Sie ja hier auch die zwei Transaktionen hintereinander schreiben können. Verschachtelte Transaktionen ergeben mehr Sinn, wenn die inneren Transaktionen in Methoden erfolgen. Angenommen, die Änderung der Transparenz soll immer eine halbe Sekunde dauern, dann könnten Sie den entsprechenden Setter des Layers so überschreiben:

- (void)setOpacity:(CGFloat)inOpacity {
[CATransaction begin];
[CATransaction setAnimationDuration:0.5];
super.opacity = inOpacity;
[CATransaction commit];
}

Listing 6.76 Standardanimationszeit für Layer-Property festlegen

Wenn Sie nun die Property opacity ändern, animiert das Programm diese Änderung immer mit einer Dauer von einer halben Sekunde. Dabei können Sie diesen Setter von nahezu beliebigen Stellen Ihres Programmcodes aufrufen, und Sie müssen sich dabei insbesondere nicht darum kümmern, ob sich die Anweisung bereits innerhalb einer Transaktion befindet.

Über explizite Transaktionen können Sie auch die impliziten Animationen von Core Animation ausschalten. Wenn Sie Layer verändern, die nicht die Standard-Layer eines Views sind, animiert Core Animation diese Änderung mit einer Dauer von einer Viertelsekunde. Häufig ist dieses Verhalten jedoch unerwünscht. Über die Klassenmethode setDisableActions: lassen sich diese impliziten Animationen ausschalten, indem Core Animation die Erzeugung von Aktionen unterdrückt. Listing 6.77 zeigt ein Beispiel für diese Methode.

[CATransaction begin];
[CATransaction setDisableActions:YES];
theLayer.position = CGPointMake(100.0, 200.0);
[CATransaction commit];

Listing 6.77 Aktionen und implizite Animationen unterdrücken


Rheinwerk Computing - Zum Seitenanfang

6.3.11Die 3. DimensionZur vorigen Überschrift

Im Gegensatz zu einem View befinden sich Layer in einem dreidimensionalen Raum. Über die Property zPosition können Sie die Anordnung von überlappenden Layern zueinander festlegen. Core Animation stellt Layer mit einer höheren z-Position vor Layern mit niedrigeren z-Positionen dar. Wenn sich Layer also überlappen, verdecken die Layer mit dem höheren Wert die mit den niedrigeren.

Im Gegensatz dazu legt die Property anchorPointZ die Lage des Layers in einem dreidimensionalen Raum fest (siehe Abbildung 6.31).

Abbildung

Abbildung 6.31 Dreidimensionales Koordinatensystem der Layer

Projektinformation

Auf der DVD finden Sie das Beispielprojekt Animation3D im Ordner Code/Apps/iOS7/Animation3D oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS7/Animation3D.

Diese App erlaubt die Änderung der Propertys zPosition und anchorPointZ im Bereich von –100 bis +100 über jeweils einen Slider. Dieses Projekt dient dabei weniger als Programmierbeispiel, auch wenn Sie darin viele Codeteile dieses Abschnitts wiederfinden. Es soll vielmehr den Unterschied zwischen den beiden Propertys verdeutlichen. Starten Sie also das Projekt im Simulator, und probieren Sie verschiedene Einstellungen aus; wenn Sie die Perspektive und die Rotation aktivieren, sehen Sie damit auch sehr schön die Auswirkungen auf die dreidimensionale Darstellung.

Der Unterschied zwischen zPosition und anchorPointZ lässt sich bei Drehungen im dreidimensionalen Raum sehr gut visualisieren: Wenn anchorPointZ nicht null ist, dreht sich der grüne Layer um den roten, wie die Erde um die Sonne. Ist diese Property hingegen null, drehen sich die beiden Layer einfach nur um ihre x-Achse, wobei der Wert von zPosition keine Auswirkung auf die Drehung hat.

Sie können zwar die Layer darin räumlich transformieren (drehen, strecken oder verschieben). Die Layer selbst bleiben hingegen flach. Beispielsweise verschwindet ein um 90° um die y-Achse gedrehter Layer, weil seine Ausdehnung in x-Richtung in diesem Fall null ist.

Die affine Transformation eines Layers beschreibt die C-Struktur CATransform3D, die eine 4×4-Matrix darstellt und auf die Sie über die Property transform zugreifen können. Sie können sie analog zu den zweidimensionalen Transformationen von Core Graphics verwenden, da es hierfür analoge Funktionen gibt. Beispielsweise entspricht der Funktion CGAffineTransformMakeScale die Funktion CATransform3DMakeScale, die jedoch einen weiteren Parameter für die z-Achse besitzt. Etwas komplizierter ist es allerdings bei CATransform3DMakeRotation; hier müssen Sie neben dem Winkel auch einen Richtungsvektor in Form einer dreidimensionalen Koordinate angeben. Dabei können Sie sich den Richtungsvektor als eine Linie vom Nullpunkt zu der Koordinate vorstellen. Die Ergebnismatrix beschreibt nun eine Drehung um diesen Vektor. Beispielsweise beschreibt die Transformation

CATransform3DMakeRotation(M_PI / 4.0, 0.0, 0.0, 1.0)

eine 45°-Drehung um die z-Achse als Rotationsachse.

Layer mit Tiefgang

Wenn Sie einen Layer um die x- oder y-Achse drehen, sieht das Ergebnis indes nach wie vor flach aus, und die Darstellung entspricht eher einer Stauchung. Das liegt daran, dass die Layer zwar in einem dreidimensionalen Raum liegen, jedoch noch keine Perspektive haben. Bei einer perspektivischen Darstellung sind die weiter hinten liegenden Bildteile kleiner als die weiter vorn liegenden.

Diesen Effekt können Sie erreichen, indem Sie jeweils einen Anteil der z-Position zu jeweils der x- und der y-Position addieren. Das verkleinert den hinten liegenden Teil des Layers und vergrößert den weiter vorn liegenden. Bei Layern mit Tiefe erzeugt das eine perspektivische Darstellung. Auch diese Berechnung lässt sich durch eine Transformationsmatrix durchführen.

Über die Property sublayerTransform legen Sie eine Transformation für die Sublayer eines Layers fest. Da Core Animation diese Transformation zusätzlich zu der Transformation des Sublayers anwendet, können Sie damit die Perspektivenberechnung durchführen.

CATransform3D theTransform = CATransform3DIdentity;
theTransform.m34 = –0.005;
theParentLayer.sublayerTransform = theTransform;

Mit dieser Transformation stellt theParentLayer seine Sublayer perspektivisch dar; er vergrößert oder verkleinert die Layer, deren anchorPointZ nicht null ist, und verzerrt um die x- oder y-Achse gedrehte Layer.

Das Beispielprojekt Pie verwendet diesen Effekt. Wenn Sie den Pieview berühren, dreht er sich um die x-Achse, wobei durch die beschriebene Transformation ein räumlicher Eindruck entsteht (siehe Abbildung 6.32).

Abbildung

Abbildung 6.32 Layer mit Perspektive

Die Layer-Property transform lässt sich auch über Property-Animationen verändern. Dazu erzeugen Sie die gewünschte Transformationsmatrix, die Sie danach über den Convenience-Konstruktor valueWithCATransform3D: in ein NSValue-Objekt umwandeln müssen, weil die Animationen ja Objekte und keine C-Strukturen erwarten.

CATransform3D theTransformation = CATransform3DMakeRotation( 
M_PI / 3.0, 1.0, 1.0, 0.0);
CABasicAnimation *theAnimation = [CABasicAnimation animation];
theTransform = CATransform3DScale(theTransform, 0.5, 0.5, 0.5);
theAnimation.fromValue =
[NSValue valueWithCATransform3D:CATransform3DIdentity];
theAnimation.toValue =
[NSValue valueWithCATransform3D:theTransform];
[theLayer addAnimation:theAnimation forKey:@"transform"];

Listing 6.78 Dreidimensionale Layer-Animation

Die Anweisungen in Listing 6.78 erzeugen eine Animation mit einer 60°-Drehung (π / 3) und einer Stauchung auf die halbe Größe. Bei der Erzeugung einer Drehung im dreidimensionalen Raum müssen Sie ja die Rotationsachse beschreiben. Das ist in diesem Beispiel der Richtungsvektor (1, 1, 0), also die Linie vom Nullpunkt zum Punkt mit den Koordinaten (1, 1, 0).

Drehungen über die Hauptachsen

Vorsicht, der Inhalt dieses Kastens kann Spuren von Mathematik enthalten, und das Lesen kann zu abstraktem Denken führen.

Sie können Rotationen um einen beliebigen Vektor auch durch ein bis drei Rotationen an den Hauptachsen x, y und z beschreiben, indem Sie den Rotationsvektor auch durch Drehungen darstellen. Die Linie des Vektors (1, 1, 0) hat beispielsweise den Winkel 45° zur x-Achse, und somit können Sie diesen Vektor als 45°-Drehung um die z-Achse ausdrücken. Die Rotation aus Listing 6.78 können Sie also auch durch zwei Transformationen wie folgt beschreiben:

CATransform3D theTransformation = CATransform3DMakeRotation( 
M_PI / 3.0, 1.0, 0.0, 0.0);
theTransform = CATransform3DRotate(theTransform,
M_PI / 4.0, 0.0, 0.0, 1.0);

In vielen Fällen reichen jedoch Rotationen, Skalierungen und Translationen an den drei Hauptachsen aus, die Sie über spezielle Animationspfade auch einfacher beschreiben können. Die Pfade haben alle einen ähnlichen Aufbau: Sie beginnen mit dem Wort transform (oder sublayerTransform), darauf folgen die Operation und der Name der Achse. Als Operationen stehen rotation, scale und translate zur Verfügung. Eine einfache Rotation um die x-Achse können Sie beispielsweise so beschreiben:

CABasicAnimation *theAnimation = [CABasicAnimation animation];
theAnimation.fromValue = [NSNumber numberWithFloat:0.0];
theAnimation.toValue = [NSNumber numberWithFloat:M_PI / 2.0];
[theLayer addAnimation:theAnimation
forKey:@"transform.rotation.x"];

Listing 6.79 Dreidimensionale Animation über einen Animationspfad

Sie können den Namen der Achse auch weglassen, um die Werte für mehrere Achsen gleichzeitig zu setzen. Die Werte in den Property-Animationen müssen Sie dann als NSArray mit jeweils drei NSValue-Objekten übergeben:

CABasicAnimation *theAnimation = [CABasicAnimation animation];
theAnimation.fromValue = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.0], nil];
theAnimation.toValue = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.5], nil];
[theLayer addAnimation:theAnimation forKey:@"transform.scale"];

Listing 6.80 Animierte Skalierung entlang mehrerer Achsen



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH, Bonn 2014
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa






Einstieg in Objective-C 2.0 und Cocoa


Zum Katalog: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo