Kapitel 16 Die eigene Sicht der Dinge vermitteln: Eigene grafische Ausgaben und Animationen

Inhalt

Die Apps, die Sie in den letzten beiden Kapiteln entwickelt haben, enthalten bereits viele Elemente, die Sie später  wenn Sie ein erfahrener Programmierer geworden sind  sehr oft wiederfinden werden. Sie haben gelernt, was MVC bedeutet und wie Sie Ihre Programme damit strukturieren können. Sie haben auch gelernt, wie Sie die existierenden Teile von Apples Entwicklungsumgebung einbinden und benutzen.

Es gibt aber Situationen, wo die grafischen Elemente, die Apple zur Verfügung stellt, alleine nicht ausreichen. Gerade für eine Spiele-App wie das Streichholzspiel aus dem vorhergehenden Kapitel wirkt eine reine Textanzeige  wie viele Hölzer sind noch übrig?  recht fade und langweilig. Hier sollte eigentlich das iPhone mehr bieten. Und genau das tut es auch. Daher lernen Sie in diesem Kapitel, wie Sie ein eigenes grafisches Element entwickeln: eine grafische Version des Streichholzhaufens!

16.1 Ich zeige Sie an  unter iOS

iOS arbeitet grundsätzlich rein grafisch. Alles, was auf dem Bildschirm eines iPhones oder iPads angezeigt wird, ist ein grafisches Element. Selbst ein Label oder ein Textfeld zeigt Text als grafische Elemente an. Das erkennen Sie daran, dass Sie einerseits die Zeichensätze und -größen frei wählen können, andererseits aber auch die gleichen Koordinaten für Position und Größe angeben, die Sie auch andernorts für alle anderen grafischen Elemente verwenden.

Sie haben bereits in Kapitel 11 erfahren, wie Objekte die Eigenschaften und Methoden ihrer Basisklasse »erben« und wie Sie diese Fähigkeit geschickt nutzen, um sich Arbeit zu sparen. Wenn also unter iOS alle grafischen Elemente die gleichen Grundeigenschaften wie Position und Größe besitzen und alle grafischen Elemente Objekte sind, warum gibt es dafür dann keine allgemeine Basisklasse, die genau diese Grundeigenschaften besitzt und ansonsten nichts weiter tut?

Die Antwort ist ganz einfach: Eine solche Klasse gibt es durchaus! Sie nennt sich UIView und ist die Vorlage für alle anderen grafischen Elemente, die auf dem iPhone-Bildschirm angezeigt werden können. UIView ist verantwortlich sowohl für die Position auf dem iPhone-Bildschirm als auch für die Größe eines Elements. Ein UIView kann ebenfalls schnell und einfach elegante Animationen erzeugen, die natürlich für alle anderen grafischen Elemente ebenfalls zur Verfügung stehen.

Das ganze Geheimnis eigener grafischer Elemente besteht nun darin, dass Sie eine neue Klasse anlegen, die UIView als Basisklasse besitzt, und für diese Klasse dann eine eigene Darstellung implementieren. Die Benutzung eines eigenen grafischen Elements ist Ihnen bereits in Abschnitt 2.7 im Listing 2.3 begegnet. Hier möchte ich nun im Detail erläutern, was dort genau passiert ist.

16.1.1. Größe und Position

Erinnern Sie sich zunächst einmal an Abbildung 12.5 in Abschnitt 12.1.3. Dort ging es um die Position und die Größe von grafischen Elementen auf dem iPhone-Bildschirm. Die Position ist der Abstand der linken oberen Kante eines Elements von der linken oberen Ecke des Bildschirms und die Größe wird in Breite und Höhe angegeben. Genau diese vier Zahlen fasst Swift bequem in einem sogenannten CGRect zusammen. Ein CGRect ist eine struct, die von der Funktion CGRectMake erzeugt wird. Dabei bekommt CGRectMake die folgenden vier Parameter:

1. die x-Position, das heißt, der Abstand vom linken Bildschirmrand,
2. die y-Position, das heißt, der Abstand vom oberen Bildschirmrand,
3. die Breite und
4. die Höhe.

Diese vier Zahlen bestimmen ein Quadrat auf dem Bildschirm und dieses Quadrat entspricht genau der Fläche, die eine Unterklasse von UIView auf dem Bildschirm einnimmt. Aber was bedeuten diese Zahlen? Sie beziehen sich auf die sogenannten Pixel. Dies ist eine Abkürzung für Picture Elements, was sich etwa mit Bildpunkten übersetzen lässt. Ein Bildpunkt ist dabei die kleinste Fläche, die Sie auf dem Bildschirm ansprechen können.

Bei einem modernen Retina-Display ist dies allerdings nicht mehr ganz richtig. Dort sind auch halbe Bildpunkte ansprechbar. So ist der Bildschirm eines iPhone 5 320 mal 568 Pixel groß. Damit belegt ein UIView, das mit einem CGRect der Form

let halbOben = CGRectMake(0, 0, 320, 284)

erzeugt wird, die gesamte obere Bildschirmhälfte eines iPhone 5. Und ein CGRect der Form

let halbRechts = CGRectMake(160, 0, 160, 568)

belegt die gesamte rechte Bildschirmhälfte.

Wenn Sie wissen möchten, welche Position und Größe in einem CGRect namens halbRechts enthalten sind, so können Sie diese folgendermaßen abfragen:

Was machen Sie aber nun mit dem CGRect? Glücklicherweise bietet die Klasse UIView die Initialisierungsmethode mit Parameter frame, mit der Sie ein UIView erzeugen, das genau die Größe eines CGRect einnimmt, also beispielsweise

// Die rechte Bildschirmhälfte auf einem iPhone 5s. 
let halbRechts = CGRectMake(160, 0, 160, 568) 
 
// Ein View, dass diese Fläche einnimmt. 
let halbRechtsView = UIView(frame: halbRechts)

Nun ist UIView die Basisklasse aller grafischen Elemente, die unter iOS angezeigt werden können. Das bedeutet, dass alle grafischen Elemente mit der oben gezeigten Methode angezeigt werden können und ihnen eine entsprechende Größe gegeben werden kann, also auch einem UILabel.


Tipp
Eine beliebige Größe funktioniert allerdings nicht bei allen Elementen, denn beispielsweise die Datumsauswahl erfordert eine bestimmte Mindestgröße.
Deshalb schauen Sie immer in die Dokumentation, ob ein bestimmtes grafisches Element eine feste Größe hat, die Sie nicht ändern können oder ob es sich Ihren Wünschen entsprechend anpassen lässt.

Nun reicht das Erzeugen der Objekte alleine noch nicht aus, um sie auch anzuzeigen. Dazu müssen Sie ein erzeugtes Objekt auch noch zu einem bestehenden hinzufügen. Das heißt, es muss mindestens ein UIView bereits angezeigt werden, damit Sie Ihr eigenes hinzufügen können. Dies hört sich schwieriger an, als es ist: Glücklicherweise bietet ein Bildschirm mit einem UIViewController bereits das UIView an, das dem Bildschirmhintergrund entspricht. In einer View-Controller-Klasse nennt sich dieses self.view. Und die Methode, um ein neues zu einem existierenden View hinzuzufügen, nennt sich addSubview. Das heißt, Sie können einfach das neu erzeugte View anzeigen, indem Sie in der Methode viewDidLoad schreiben:

// Definiere ein Quadrat, das die rechte Bildschirmhälfte 
// auf einem iPhone 5s ausfüllt. 
let halbRechts = CGRectMake(160, 0, 160, 568) 
 
// Definiere ein UIView mit diesem Quadrat. 
let halbRechtsView = UIView(frame: halbRechts) 
 
// Füge dieses UIView zum Bildschirm hinzu. 
self.view.addSubview(halbRechtsView)

Damit Sie aber auch tatsächlich etwas sehen können, macht es Sinn, wenn Sie dem View eine eigene Farbe geben, zum Beispiel ein kräftiges Rot:

halbRechtsView.backgroundColor = UIColor.redColor()

Dabei ist backgroundColor eine Eigenschaft, die die Hintergrundfarbe angibt. Da dies eine Eigenschaft von UIView ist, können Sie diese  Sie ahnen es schon  bei allen grafischen Elementen setzen. Der Typ ist eine Klasse namens UIColor und die Methode redColor() erzeugt ein Objekt vom Typ UIColor, das die Farbe Rot repräsentiert.

Erzeugen Sie in Xcode ein neues Projekt mit dem Namen »StreichholzAnzeiger« aus der Vorlage SINGLE VIEW APPLICATION und fügen Sie die obigen Zeilen Programmtext in die viewDidLoad-Methode ein. Das Ergebnis wird dann ein Bildschirm sein, der in der linken Hälfte weiß und der rechten Hälfte rot ist.

16.1.2. Dynamische Größenanpassung

Bei der Einrichtung von Bildschirmen habe ich Ihnen bisher immer eine Reihe von Ausrichtungsregeln genannt, mit denen Sie sichergestellt haben, dass die Anzeige auf verschiedenen Geräten mit verschiedenen Bildschirmgrößen und Orientierungen richtig funktioniert.

Das rote halbRechtsView aus dem obigen Beispiel tut das nicht: Es funktioniert nur auf einem iPhone 5s und auch nur in der Portrait-Darstellung. Wenn Sie also wirklich eine App schreiben wollten, die auf verschiedenen Geräten und auch in der Landscape-Orientierung richtig arbeitet, so müssen Sie die Ausrichtungsregeln in Ihrem Programmtext berücksichtigen. Dies ist leider recht kompliziert und geht über den Rahmen dieses Buches hinaus.

Wenn Sie mehr darüber erfahren wollen, so empfehle ich Ihnen den »Auto Layout Guide: Understanding Auto Layout« in der Apple-Dokumentation. Die Klasse für Ausrichtungsregeln selbst lautet NSLayoutConstraint.

Im Folgenden werde ich verschiedene Varianten vorstellen, eine grafische Streichholzanzeige ohne Ausrichtungsregeln mit festem Layout zu erzeugen. Für diesen Fall macht dieser Ansatz Sinn, denn für eine solche Anzeige wären Ausrichtungsregeln unnötig langsam und kompliziert. Die fertige Anzeige werden Sie dann in die »Streichholz«-App mit Ausrichtungsregeln einsetzen, wobei die Größe fest vorgegeben ist.


Tipp
Für eigene grafische Unterklassen von UIView empfehle ich nur sehr bedingt, Ausrichtungsregeln zu verwenden. Sie sind sehr komplex und es ist zu leicht möglich, nur schwer zu entdeckende Fehler einzubauen. In diesem Fall sollten Sie die Positionen lieber selbst »von Hand« berechnen.

16.2 Der grafische Streichholzhaufen

Nun werden Sie zwei verschiedene Varianten von Streichhölzern bauen: Eine Variante durch einen Kombination von UIView-Objekten und eine Variante durch echte grafische Linien und Anzeigen.

16.2.1. Streichhölzer Variante 1

Mit Kombinationen von UIView können Sie bereits grundsätzlich einen Streichholzhaufen programmieren: Sie basteln einfach eine Reihe von UIView-Objekten, setzen die entsprechenden Farben und platzieren die Objekte so, dass sie aussehen wie Streichhölzer.

IMG

Abbildung 16.1 Ein einzelnes Streichholz, dargestellt durch die Klasse »StreichholzView«

Wie konstruieren Sie nun also das Streichholz aus den mehreren UIView-Objekten? Die direkteste Möglichkeit besteht darin, zwei Objekte vom Typ UIView zu erzeugen und deren Hintergrundfarben auf Rot (für den Streichholzkopf) und auf Gelb (für den Streichholzkörper) zu setzen. Diese Idee ist in Abbildung 16.1 gezeigt. Beachten Sie, dass Sie mit der Klasse StreichholzView zwar einerseits eine eigene Klasse erzeugen, die eigene Funktionalität mitbringt, aber dennoch als Basisklasse UIView besitzt. Andererseits benutzen Sie aber auch direkt zwei Objekte vom Typ UIView, damit Sie Ihre eigene Funktionalität umsetzen können.

Dies passiert durchaus öfter, wenn Sie mit Objekten eigene Programme schreiben: Sie benutzen Objekte, wenn es nützlich und sinnvoll ist. Dabei können Sie durchaus die Basisklasse oder gar die Objekte selbst benutzen, mit denen Sie gerade arbeiten! Gerade bei UIView kommt es häufig vor, dass Sie verschiedene Objekte, die alle UIView als Basisklasse haben, ineinander verschachteln. So fügen Sie alle grafischen Elemente auf dem iPhone-Bildschirm ein, der immer eine Instanz von UIView ist. Alle grafischen Elemente haben ihrerseits UIView (in der Regel über mehrere weitere Vererbungen hinweg) als Basisklasse.

Wo und wie erzeugen Sie aber am besten diese zusätzlichen UIView-Objekte? Genau dafür können Sie die Initialisierungsmethode init mit dem Parameter frame verwenden. Für diese Methode bietet sich ein bestimmtes Muster an, das von Apple vorgegeben ist: Sie kopieren eine Vorlage und fügen dann wieder Ihren eigenen Programmtext an der geeigneten Stelle ein.

Zu diesem Zweck fügen Sie eine neue Datei zum Projekt »StreichholzAnzeiger« hinzu und nennen sie StreichholzView.swift. Dort schreiben Sie eine neue Klasse namens StreichholzView mit Basisklasse UIView. Das Ergebnis ist in Listing 16.1 abgedruckt. Diese Klasse erzeugt die Streichholz-Darstellung in zwei verschiedenen Situationen:

1. Bei der Erzeugung eines StreichholzView-Objektes im Programmtext selbst mit dem Aufruf StreichholzView(frame:).
2. Bei der Erzeugung des Objektes durch den grafischen Editor in einer Storyboard-Datei. Hierfür benutzt Xcode den Aufruf StreichholzView(coder:).

Beide init-Methoden rufen eine Hilfsmethode setup() auf, die die notwendigen Arbeiten übernimmt.


Tipp
Ich empfehle generell für alle Unterklassen von UIView, beide init-Methoden mit Parameter frame und mit Parameter coder zu implementieren. Damit kann Ihr eigenes View von anderen Programmierern ganz flexibel eingesetzt werden.

/// Darstellung eines einzelnen Streichholzes. 
class StreichholzView: UIView { 
 
  /// Wird benutzt, um ein UIView im Programmtext zu erzeugen. 
  override init(frame: CGRect) { 
    super.init(frame: frame) 
    self.setup() 
  } 
 
  /// Wird benutzt, um ein UIView von einem Storyboard aus zu 
  /// erzeugen. 
  required init?(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder) 
    self.setup() 
  } 
 
  /// Konstruiere alle Hilfsobjekte. 
  func setup() { 
    // Das Quadrat für den Streichholzkopf. 
    let kopfForm = CGRectMake(0, 0, 5, 10) 
 
    // Das Quadrat für den Streichholzkörper. 
    let koerperForm = CGRectMake(0, 0 + 10, 5, 45) 
 
    // Erzeuge ein UIView für den Kopf. 
    let kopf = UIView(frame: kopfForm) 
 
    // Erzeuge ein UIView für den Körper. 
    let koerper = UIView(frame: koerperForm) 
 
    // Mache den Kopf rot. 
    kopf.backgroundColor = UIColor.redColor() 
 
    // Mache den Körper gelb. 
    koerper.backgroundColor = UIColor.yellowColor() 
 
    // Füge Kopf und Körper zum aktuellen UIView hinzu. 
    self.addSubview(kopf) 
    self.addSubview(koerper) 
  } 
}

Listing 16.1 Implementierung der Klasse »StreichholzView« zur Darstellung eines einzelnen Streichholzes

Eine Besonderheit (ich vermeide bewusst das Wort »Einschränkung«) gibt es allerdings noch:

Die grafische Darstellung in Abbildung 16.1 erlaubt nur eine feste Größe für ein Streichholz, nämlich 5 Pixel breit und 50 Pixel hoch. Grundsätzlich könnten Sie das Streichholz immer so skalieren, dass es das UIView ausfüllt und entweder etwas größer oder etwas kleiner ist, je nachdem, welche Größe im Parameter frame angegeben ist  auch ohne Ausrichtungsregeln zu benutzen. Das sieht allerdings unter Umständen nicht gut aus  zwei Klötzchen in Bildschirmgröße oder ein drei Millimeter großes Strichelchen wird niemand als Streichholz identifizieren können. Deswegen ist es an dieser Stelle sinnvoller, wenn Sie eine feste Größe vorschreiben, damit ein anderer Programmierer nicht versehentlich hässliche Streichhölzer erzeugt.

Das macht Apple genauso: Bestimmte Elemente wie beispielsweise ein Textfeld haben eine vorgegebene Höhe, die Sie nicht ohne Weiteres ändern können. Diese Größe sieht gut aus und funktioniert für die meisten Benutzer einwandfrei. Ändern dürfen Sie sie deswegen nur, wenn Sie eine komplett neue Textfeld-Klasse entwickeln (was wirklich eine Menge Arbeit ist).

Aus demselben Grund macht es an dieser Stelle Sinn, eine feste Größe vorzugeben und anderen Programmierern nicht zu erlauben, sie zu ändern! Das ist einer der Gründe, warum ich nicht empfehle, bei eigenen grafischen Anzeigen Ausrichtungsregeln zu benutzen.

import UIKit 
 
/// Hauptbildschirm zum Testen der StreichholzAnzeige. 
class ViewController: UIViewController { 
 
  override func viewDidLoad() { 
    super.viewDidLoad() 
 
    // Definiere den Bereich, in dem das Streichholz angezeigt werden 
    // soll. 
    let streichholzBereich = CGRectMake(20, 20, 50, 100) 
    // Erzeuge ein StreichholzView. 
    let anzeige = StreichholzView(frame: streichholzBereich) 
 
    // Füge es zum Bildschirm hinzu. 
    self.view.addSubview(anzeige) 
  } 
 
}

Listing 16.2 Implementierung der Klasse »ViewController« mit einem einzelnen Streichholz der Klasse »StreichholzView«

Um ein StreichholzView mit einem einzelnen Streichholz einmal auszuprobieren, können Sie das grafische Element einfach direkt in der Methode viewDidLoad der Klasse ViewController einbauen und das Ergebnis begutachten, siehe Listing 16.2 . Starten Sie einmal die App und vergewissern Sie sich, dass Sie ein einzelnes kleines Streichholz in der linken oberen Ecke sehen!

16.2.2. Streichhölzer Variante 2  Sie können auch anders

Die gerade gezeigte Methode funktioniert ganz gut: Sie können damit beliebige Grafiken zusammensetzen und auch mehrere Streichhölzer darstellen. Sie hat allerdings den Nachteil, dass sie das »Legostein-Prinzip« benutzt: Wenn Sie mal Bilder haben, die nicht aus Rechtecken zusammengesetzt sind, geht das leider gar nicht. Eine Alternative haben Sie in Abschnitt 14.2.4 kennengelernt: Sie können Bilder zeichnen oder auch fotografieren und diese dann als »Image View« in Ihre App einfügen  die entsprechende Klasse nennt sich UIImageView. Damit lässt sich ein Bild von einem Streichholz anstelle der Klötzchen aus dem vorherigen Abschnitt verwenden.

In diesem Abschnitt möchte ich Ihnen eine weitere Alternative vorstellen, die ebenfalls die Einschränkung der Legoklötzchen aufhebt. Dabei benutzen Sie eine Methode namens drawRect, die es erlaubt, beliebige Formen und Grafiken in Ihre eigene Klasse zu zeichnen, solange diese UIView als Basisklasse besitzt.

Apple bietet zu diesem Zweck unter dem Namen Quartz 2D eine Reihe von sehr leistungsfähigen und flexiblen Grafikfunktionen an. Die meisten dieser Funktionen sind tatsächlich Funktionen und keine Methoden auf Objekten. Es gibt ein paar Objektaufrufe, aber für die meisten Aufgaben müssen Sie Funktionen verwenden. Eine vollständige Auflistung aller dieser Grafikfunktionen und ihrer vielfältigen Möglichkeiten würde den Rahmen dieses Buches bei Weitem sprengen, die vollständige ausführliche Anleitung finden Sie in der Apple-Dokumentation unter dem Namen »Quartz 2D Programming Guide«.

Die Grundfunktionen bestehen darin, beliebige Formen zu zeichnen und dabei die Strichstärken, Strichfarben und Füllfarben zu setzen. Weitergehende Funktionen (die ich im Folgenden allerdings nicht erläutern werde) erlauben es, beliebige Füllmuster (einschließlich Gradienten) zu wählen, Schatten hinzuzufügen und Grafiken zu strecken und zu drehen. Die letzte Funktionalität werde ich später allerdings für den Fall eines ganzen UIView-Objekts für die Zwecke von Animationen beschreiben.

Die Grundlage der Grafikfunktionen ist der sogenannte Context, auf Deutsch könnte man ihn mit Prozesskontext übersetzen. Dieser Prozesskontext enthält die gerade gewählten Einstellungen für Strichstärke, Farben und Skalen. Außerdem weiß der Prozesskontext, in welchem View Sie gerade arbeiten. Wenn Sie eine neue Linie zeichnen, so werden diese Einstellungen benutzt, ohne dass Sie alle einzelnen Funktionen jedes Mal aufs Neue manuell setzen müssen. Umgekehrt können Sie mit einer einzelnen Änderung alle zukünftigen Operationen beeinflussen.

Der Prozesskontext ist vom Datentyp CGContext und wird mit der Funktion UIGraphicsGetCurrentContext() erzeugt. Wenn Sie diese Funktion innerhalb der Methode drawRect mittels

let context = UIGraphicsGetCurrentContext()

aufrufen, so werden alle Funktionen, die sich auf die Variable context berufen, innerhalb des zugehörigen UIView ausgeführt. Für die Zwecke dieses Kapitels benötigen Sie die folgenden Funktionalitäten:

1. Farben auswählen: Sie können die aktuelle Farbe auf zwei Wegen auswählen: erstens, indem Sie
UIColor.yellowColor().set()
benutzen. Dabei wird für zukünftige Operationen die Farbe »Gelb« (Englisch »yellow«) verwendet. Zweitens können Sie auch die Funktion
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 0.0, 1.0)
verwenden. Letztere erlaubt Ihnen, Farben in Form von Rot-Grün-Blau-Farbanteilen anzugeben und zuletzt sogar einen Transparenzfaktor auszuwählen. Die letztere Methode ist bei Weitem die leistungsfähigere, allerdings bedeutet sie auch deutlich mehr Arbeit, denn Sie werden wahrscheinlich erst mal experimentieren müssen, wie denn die Rot-Grün-Blau Anteile eigentlich sein müssen, um eine gewünschte Farbe zu erzeugen. Die im Beispiel gezeigte Kombination hat Rot- und Grünanteile von 1.0 und einen Blauanteil von 0.0 sowie keine Transparenz (dies entspricht dem letzten Parameter 1.0). Dies ist die Farbe »Gelb«, weswegen die zweite Anweisung identisch ist mit der ersten  wobei sie allerdings deutlich schwerer zu lesen und zu verstehen ist. Deswegen empfehle ich Ihnen im Folgenden, so weit wie möglich die erste Methode zu verwenden. Die möglichen Farben finden Sie auf der Hilfeseite von UIColor.
2. Einen Startpunkt wählen: Sie können eine beliebige Fläche zeichnen und diese anschließend mit einer Füllfarbe füllen lassen. Die Randstriche dieser Fläche dürfen sogar eine eigene Farbe und eine eigene Strichstärke haben. Damit das funktioniert, müssen Sie zunächst einen Startpunkt vorgeben. Wenn die Koordinaten des Startpunkts startX und startY lauten, dann können Sie diese folgendermaßen als Startpunkt festlegen:
CGContextMoveToPoint(context, startX, startY)
3. Eine gerade Linie hinzufügen: Um eine gerade Linie vom letzten Punkt zu einem neuen Punkt weiterX/weiterY zu zeichnen, so verwenden Sie die folgende Funktion:
CGContextAddLineToPoint(context, weiterX, weiterY)

Eine solche Linie kann zu einem beliebigen anderen Punkt gehen. Dieser darf sogar außerhalb des Bildschirms liegen. Sie können diese Methode mehrfach hintereinander aufrufen, um längere Linien und damit komplexere Formen zu zeichnen.

4. Eine Fläche füllen: Wenn Sie eine Fläche mit den Funktionen CGContextMoveToPoint und mehreren Aufrufen von CGContextAddLineToPoint gezeichnet haben, so können Sie sie mit der Funktion CGContextDrawPath füllen:
CGContextDrawPath(context, .FillStroke)

Dabei ist der erste Parameter wieder der bekannte Prozesskontext und der zweite, eine Aufzählung namens CGPathDrawingMode, gibt an, dass die Fläche mit der Strichfarbe gefüllt werden soll. Es gibt hier noch eine Besonderheit:

Wenn die Fläche nicht geschlossen ist  das heißt, der Startpunkt nicht gleich dem Endpunkt ist , dann wird noch eine weitere Begrenzungslinie automatisch eingefügt, die vom Endpunkt zurück zum Startpunkt läuft. Das zeige ich weiter unten am Beispiel.

5. Eine Ellipse zeichnen: Eine Ellipse ist eine etwas kompliziertere Geschichte und wenn Sie diese selbst aus annähernd geraden Teilstücken mit den obigen Funktionen zeichnen müssten, wäre das ein ganzes Stück Arbeit. Glücklicherweise hat Apple Ihnen diese Arbeit abgenommen: iOS bietet eine Funktion an, die selbstständig eine Ellipse so zeichnen kann, dass sie in ein CGRect hineinpasst. Wenn das CGRect den Namen kopfForm besitzt, so können Sie die folgenden zwei Aufrufe benutzen:
CGContextAddEllipseInRect(context, kopfForm) 
CGContextDrawPath(context, .FillStroke)
Der erste Aufruf erzeugt dabei die gewünschte Ellipse und der zweite sorgt wieder dafür, dass sie in der aktuellen Zeichenfarbe ausgefüllt wird.

Nun haben Sie die notwendigen Bausteine beisammen, um das Streichholz mit den Grafikfunktionen von Apple direkt zu zeichnen. Ein konkreter Vorschlag, wie das Streichholz aussehen könnte, ist in Abbildung 16.2 zu sehen. Zunächst wird der Körper des Streichholzes in gelber Farbe wie vorher als Rechteck gezeichnet. Dann kommt der Kopf in roter Farbe darauf, allerdings dieses Mal als Ellipse in einem Bereich, der etwas größer ist als vorher der Körper. Dadurch sieht das Streichholz besser aus.

IMG

Abbildung 16.2 Zeichnung eines einzelnes Streichholzes mit den Grafikfunktionen von Apple

import UIKit 
 
/// Darstellung eines einzelnen Streichholzes. 
class StreichholzViewAlt: UIView { 
 
  /// Wird benutzt, um ein UIView im Programmtext zu erzeugen. 
  override init(frame: CGRect) { 
    super.init(frame: frame) 
    self.setup() 
  } 
 
  /// Wird benutzt, um ein UIView von einem Storyboard aus zu 
  /// erzeugen. 
  required init?(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder) 
    self.setup() 
  } 
 
  /// Konfiguriere dieses View. 
  func setup() { 
    // Setze den eigenen Hintergrund auf "durchsichtig" 
    // ("clearColor"). 
    self.backgroundColor = UIColor.clearColor() 
  } 
 
  override func drawRect(rect: CGRect) { 
    // Speichere den aktuellen Prozesskontext in 
    // der Konstanten context. 
    let context = UIGraphicsGetCurrentContext() 
 
    // Zeichne den Streichholzkörper als Quadrat 
    // in gelber Farbe. 
    UIColor.yellowColor().set() 
    CGContextMoveToPoint(context, 5, 5) 
    CGContextAddLineToPoint(context, 10, 5) 
    CGContextAddLineToPoint(context, 10, 55) 
    CGContextAddLineToPoint(context, 5, 55) 
 
    // Fülle den Streichholzkörper mit der 
    // aktuellen Farbe (also Gelb). 
    CGContextDrawPath(context, .FillStroke) 
 
    // Zeichne den Streichholzkopf als Ellipse 
    // mit roter Farbe. 
    UIColor.redColor().set() 
    let kopfForm = CGRectMake(2, 0, 11, 20) 
    CGContextAddEllipseInRect(context, kopfForm) 
 
    // Fülle den Streichholzkopf mit der 
    // aktuellen Farbe (also Rot). 
    CGContextDrawPath(context, .FillStroke) 
  } 
}

Listing 16.3 Implementierung der Klasse »StreichholzViewAlt« zur Darstellung eines einzelnen Streichholzes

Die konkrete Umsetzung ist in Listing 16.3 in der Klasse StreichholzViewAlt zu sehen. In der setup-Methode setze ich noch die Hintergrundfarbe des Views auf »durchsichtig« (die Farbe clearColor), denn bei einem selbst gezeichneten View ist diese normalerweise Schwarz.

Zum Ausprobieren müssen Sie in diesem Fall nur in der Methode viewDidLoad die Klasse StreichholzView durch StreichholzViewAlt ersetzen.

Die eigentliche Hauptarbeit passiert in der Methode drawRect von StreichholzViewAlt. Hier wird zunächst der passende Prozesskontext in der Konstanten context gespeichert. Dann wird der Streichholzkörper in gelber Farbe als Rechteck gezeichnet. Beachten Sie, dass Sie hier nur die ersten drei Linien des Rechtecks selbst zeichnen müssen, die vierte Linie (vom Punkt (5, 55) zum Startpunkt (5, 5)) wird automatisch gezeichnet, sobald das Rechteckt mit der Funktion CGContextDrawPath gefüllt wird.

Zuletzt wird der Streichholzkopf als Ellipse gezeichnet. Die Ellipse wird so gezeichnet, dass sie in ein Rechteck passt, das drei Pixel an jeder Seite breiter ist als der Streichholzkörper und fünf Pixel oberhalb des Körpers beginnt und 20 Pixel hoch ist  vergleichen Sie das nochmals mit Abbildung 16.2. Anschließend wird die Ellipse in der Strichfarbe (in diesem Fall ist es die rote Farbe) gefüllt. Und fertig ist das gesamte Streichholz!

16.3 Einen Haufen machen

Nun wollen Sie ja eigentlich nicht nur einzelne Streichhölzer malen, sondern in Wahrheit den ganzen Streichholzhaufen grafisch darstellen. Das ist jetzt kein Problem mehr! Wo Sie jetzt schon ein einzelnes Streichholz zeichnen können, ist es bis zum Haufen auch nicht mehr weit.

Um dieses Ziel zu erreichen, erzeugen Sie eine weitere Klasse namens HaufenView mit Basisklasse UIView. Die Aufgabe des HaufenView besteht darin, die gewünschte Anzahl von StreichholzView-Objekten zu erzeugen und an der richtigen Position darzustellen. Der Bequemlichkeit halber soll HaufenView ähnliche Methoden wie das Datenmodell besitzen:

Die letzere Methode scheint nicht unbedingt nötig zu sein, denn im Gegensatz zum Datenmodell ist es nicht die Aufgabe der Präsentation, sich damit zu beschäftigen, wie viele Hölzer entfernt werden dürfen. Sie hat allerdings einen bestimmten Sinn, wie Sie im nächsten Abschnitt über Animationen sehen werden.

Um die einzelnen Hölzer darzustellen, benutzen Sie wieder die Methode addSubview der Basisklasse UIView von HaufenView. Sie können umgekehrt auch die Sammlung aller Objekte, die mit addSubview hinzugefügt wurden, bekommen, indem Sie die Eigenschaft subviews eines jeden UIView-Objekts abfragen. Der Rückgabewert ist ein Array, also liefert beispielsweise

let hoelzerViews = self.subviews

innerhalb einer Methode der Klasse HaufenView alle StreichholzView-Objekte, die es vorher mit addSubview hinzugefügt hat.

Um ein einzelnes StreichholzView anschließend wieder zu entfernen, benutzen Sie die Methode removeFromSuperview des StreichholzView-Objekts. Dies ist eine wichtige Besonderheit von UIView-Objekten: Während Sie die addSubview-Methode von HaufenView benutzen, um neue Views hinzuzufügen, müssen Sie eine Methode  nämlich removeFromSuperview  von StreichholzView benutzen, um es wieder zu entfernen. Sie können ein View nicht direkt aus einem anderen View entfernen, zu dem Sie es hinzugefügt haben! Stattdessen müssen Sie das hinzugefügte View bitten, sich selbst wieder zu entfernen!

Eine letzte Frage müssen Sie noch beantworten, bevor Sie die Klasse HaufenView implementieren: Wie sollen mehrere Streichhölzer am Bildschirm dargestellt werden? Sie sollen minimal kein Holz und maximal 24 Hölzer gleichzeitig darstellen können. Die minimale Größe eines Streichholzes der Klasse WSStreichholzView ist 20 Pixel breit und 55 Pixel hoch, wobei Sie sinnvollerweise etwas Platz in alle Richtungen übrig behalten sollten. Deswegen ist es besser, wenn Sie eine Breite von 30 Pixeln einplanen und eine Höhe von 80 Pixeln. Wenn Sie noch jeweils zehn Pixel an den Bildschirmrändern übrig lassen, so verbleibt eine Breite von 300 Pixeln, auf der Sie Hölzer platzieren können  damit passt ein HaufenView eigentlich immer auf den Bildschirm auch des kleinsten iPhones. Dies entspricht bei 30 Pixeln pro Holz insgesamt zehn Hölzern nebeneinander. In der Höhe brauchen Sie dann drei Zeilen, um insgesamt zwei volle Reihen (mit jeweils zehn Hölzern) und eine halbvolle Reihe (mit vier Hölzern) darstellen zu können. Bei einer Höhe von 80 Pixeln pro Streichholz sind das insgesamt 240 Pixel Höhe.

Erzeugen Sie nun eine neue Datei namens HaufenView.swift und schreiben Sie dort die Implementierung aus Listing 16.4 hinein. Die Methode setzeHoelzer funktioniert folgendermaßen:

1. Alle alten Instanzen von StreichholzView werden entfernt. Diese sind alle Subviews des HaufenView, weswegen sie mit der Methode subviews als Array zurückgeliefert werden. Dann wird auf jedes einzelne die Methode removeFromSuperview angewendet. Beachten Sie, dass die Schleife nicht durchlaufen wird, wenn subviews ein leeres Array zurückliefert.
2. Die Anzahl der benötigten Zeilen wird berechnet, indem die Gesamtzahl der Hölzer durch 10 geteilt wird. Dies müssen Sie machen, da pro Zeile jeweils maximal zehn Hölzer angezeigt werden. Dabei wird abgerundet, das heißt, das Ergebnis ist entweder 0, 1 oder 2.
3. Die Anzahl der Hölzer in der letzten Zeile wird berechnet. Dies ist die Zahl der Hölzer, die nicht in vollen Zeilen zu finden ist. Dafür müssen Sie die Zeilenzahl mit 10 multiplizieren (das sind alle Hölzer, die in vollen Zeilen stecken) und diese von der gewünschten Gesamtzahl abziehen. Der Fall, dass eine Zeile nicht alle 10 Hölzer enthält, kann ja nur in der letzten Zeile auftreten. Deswegen brauchen Sie diese Berechnung nur bei der letzten Zeile vorzunehmen.
4. Sie zeichnen die »vollen« Zeilen, das heißt, die Zeilen, die alle zehn Hölzer enthalten. Dies können, wie oben bereits gezeigt, entweder null oder eine oder zwei Zeilen sein. Beachten Sie, dass die Schleife nicht durchlaufen wird, wenn es tatsächlich null sein sollten.
5. Sie zeichnen alle Hölzer der letzten Zeile nebeneinander.
6. Der Datentyp CGFloat ist eine Variante von Float in den grafischen Funktionen. In der Sprache Objective-C waren diese Datentypen im Programmtext identisch, in Swift müssen Sie bei Berechnungen explizit angeben, welche Variante Sie benötigen.

Für die Methode entferneHoelzer können Sie sich die Arbeit besonders einfach machen: Sie bekommen nämlich wieder über die Eigenschaft subviews alle vorhandenen Hölzer als Array. In Abschnitt 10.1.1 haben Sie gesehen, wie Sie die Methode lastlast eines Array benutzen, um das jeweils letzte Objekt zu bekommen. Dieses können Sie direkt entfernen. Wenn Sie diese Methode nun einfach anzahl-mal hintereinander aufrufen, wird die gewünschte Anzahl Hölzer automatisch entfernt.

import UIKit 
 
/// Anzeige einer Sammlung von Streichhölzern. 
class HaufenView: UIView { 
 
  /// Wird benutzt, um ein UIView im Programmtext zu erzeugen. 
  override init(frame: CGRect) { 
    super.init(frame: frame) 
    self.setup() 
  } 
 
  /// Wird benutzt, um ein UIView von einem Storyboard aus zu 
  /// erzeugen. 
  required init?(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder) 
    self.setup() 
  } 
 
 
  /// Konfiguriere dieses View. 
  func setup() { 
    // Setze den eigenen Hintergrund auf 
    // "durchsichtig" ("clearColor"). 
    self.backgroundColor = UIColor.clearColor() 
  } 
 
  /// Setze eine feste Anzahl von Hölzern. 
  func setzeHoelzer(anzahl: Int) { 
    // Entferne alle alten Hölzer. 
    let alteHoelzer = self.subviews 
    for holz in alteHoelzer { 
      holz.removeFromSuperview() 
    } 
 
    // Bestimme die Zahl der notwendigen Zeilen. 
    let zeilen = anzahl / 10 
 
    // Bestimme die Zahl der Hölzer in der letzten Zeile. 
    let anzahlLetzteZeile = anzahl -- (zeilen * 10) 
 
    // Zeichne alle vollen Zeilen. 
    for var zeile=0; zeile<zeilen; zeile+=1 { 
      for var spalte=0; spalte<10; spalte+=1 { 
        // Bestimmte die Position eines Streichholzes 
        // aus Zeile und Spalte. 
        let position = CGRectMake(CGFloat(10+spalte*30), 
                           CGFloat(0+zeile*80), 
                           30, 80) 
 
        // Erzeuge ein neues StreichholzView an der 
        // gewünschten Position. 
        let streichholz = StreichholzView(frame: position) 
 
        // Füge es hinzu. 
        self.addSubview(streichholz) 
      } 
    } 
 
 
 
    // Zeichne die letzte Zeile. 
    for var spalte=0; spalte<anzahlLetzteZeile; spalte+=1 { 
      // Bestimme die Position eines Streichholzes der 
      // letzten Zeile. 
      let position = CGRectMake(CGFloat(10+spalte*30), 
                         CGFloat(0+zeilen*80), 
                         30, 80) 
 
      // Erzeuge ein neues WSStreichholzView an der 
      // gewünschten Position. 
      let streichholz = StreichholzView(frame: position) 
 
      // Füge es hinzu. 
      self.addSubview(streichholz) 
    } 
  } 
 
  /// Entferne eine bestimmte Zahl von Hölzern. 
  func entferneHoelzer(anzahl: Int) { 
    // Entferne eine bestimmte Zahl von Hölzern. 
    for var i=0; i<anzahl; i+=1 { 
      // Entferne das letzte Holz (wenn es existiert). 
      self.subviews.last?.removeFromSuperview() 
    } 
  } 
}

Listing 16.4 Implementierung der Klasse HaufenView zur Darstellung aller Streichhölzer

Sie können Ihren neuen HaufenView direkt im vorhandenen Projekt »StreichholzView« ausprobieren, indem Sie die viewDidLoad-Methode der Klasse ViewController wie in Listing 16.5 gezeigt modifizieren.

override func viewDidLoad() { 
  super.viewDidLoad() 
 
  // Bestimme Position und Größe des Haufens. 
  let haufenPosition = CGRectMake(10, 10, 300, 240) 
 
  // Erzeuge das WSHaufenView--Objekt. 
  let haufen = HaufenView(frame: haufenPosition) 
 
 
  // Füge es zum Bildschirm hinzu. 
  self.view.addSubview(haufen) 
 
  // Setze die Zahl der Streichhölzer auf 24. 
  haufen.setzeHoelzer(24) 
}

Listing 16.5 Einbinden der neuen Klasse »HaufenView« zur Darstellung aller Streichhölzer

Starten Sie die App und vergewissern Sie sich, dass sie richtig funktioniert, wenn Sie eine unterschiedliche Zahl von Hölzern zwischen 0 und 24 angeben. Genau genommen funktioniert sie sogar für bis zu 30 Hölzer, obwohl diese Funktionalität gar nicht benötigt wird.

16.4 Grafische Animationen

Sie haben jetzt schon eine funktionierende Darstellung eines Streichholzhaufens und können diese Darstellung im eigentlichen Streichholzspiel verwenden. Ich zeige Ihnen aber jetzt noch etwas ganz Besonderes: nämlich die Möglichkeit, verschwindende Streichhölzer grafisch zu animieren! Und das Beste dabei: Sie müssen nur ganz wenig eigenen Programmtext dafür schreiben, da die Klasse UIView Ihnen bereits einen Großteil der Arbeit abnimmt!

16.4.1. Was können Sie grafisch animieren?

Zunächst einmal möchte ich einige neue Eigenschaften aller UIView-Objekte vorstellen und bei dieser Gelegenheit auch diejenigen wiederholen, die Sie bereits kennen. Eine neue Eigenschaft ist die Eigenschaft alpha. Dies ist eine Fließkommazahl zwischen 0 und 1, die die Transparenz, also die Durchsichtigkeit, angibt. Dabei bedeutet 0, dass das Objekt vollständig durchsichtig ist, und 1, dass es vollständig opak, also nicht durchsichtig ist. Die Farbe clearColor hat beispielsweise einen alpha-Wert von 0 und ist daher vollständig transparent.

Die Eigenschaft alpha ist übrigens die gleiche Zahl, die Sie bereits beim letzten Parameter der Funktion CGContextSetRGBStrokeColor kennengelernt haben.

Schließlich gibt es die Eigenschaft transform, die es erlaubt, das UIView zu drehen und zu strecken. Dies wird durch einen Datentyp namens CGAffineTransform ausgedrückt. Um diese Drehungen und Streckungen vollständig zu verstehen, ist allerdings eine Menge Mathematik erforderlich. Zum Glück hat Apple Hilfsfunktionen geschaffen, die Ihre Arbeit erheblich erleichtern und Ihnen ein paar Monate Mathematikstudium ersparen. Die zwei wichtigsten Funktionen sind die folgenden:

Tabelle 16.1 fasst nochmals die wichtigsten Eigenschaften, die Sie an einem UIView einstellen können, zusammen.

Eigenschaft Datentyp Bedeutung
frame CGRect Bildschirmposition und -breite
backgroundColor UIColor Hintergrundfarbe
alpha CGFloat Durchsichtigkeit (von 0 bis 1)
transform CGAffineTransform Drehung und Streckung

Tabelle 16.1 Einige Eigenschaften eines »UIView«-Objekts, die Sie verändern können

16.4.2. Wie Sie Animation durchführen

Warum habe ich diese Eigenschaften aufgelistet? Der Grund ist, dass das UIView alle diese Eigenschaften grafisch während einer Animation verändern kann! Sie können beispielsweise ein Streichholz »umwerfen« und gleichzeitig ausblenden. Oder Sie können es ganz klein werden lassen, sodass es schließlich ganz verschwindet!

Die Methode, mit der Sie diese ganze »Magie« bewirken können, nennt sich animateWithDuration und ist eine Klassenmethode, siehe auch den fortgeschrittenen Abschnitt 11.4.4. Diese Methode hat als ersten Parameter die Zeitdauer der Animation in Sekunden. Von dieser Methode gibt es verschiedene Varianten mit unterschiedlicher Anzahl von Parametern. Im Folgenden bespreche ich die Form

UIView.animateWithDuration(3.0, // Dauer in Sekunden. 
    animations: { () in 
      // Resultat der Animation. 
    }, completion: { (Abgebrochen: Bool) in 
      // Abschluss der Animation. 
    })

Der zweite Parameter mit Namen animations bekommt einen Block, in dem die Resultate der Animation beschrieben werden. Also alle Eigenschaften von allen Views auf dem Bildschirm, die nach dem Ende der Animation gesetzt werden sollen. Dadurch können Sie mit wenigen Zeilen Programmtext sehr komplizierte Abläufe und Animationen erreichen! Wollen Sie beispielsweise die Hintergrundfarbe von Rot auf Schwarz ändern, so setzen Sie innerhalb des Blocks

self.backgroundColor = UIColor.blackColor()

und die Klasse UIView macht den Rest  die ganzen Umrechnungen, Zwischenfarbtöne, Verlaufskurven und so weiter. Glauben Sie mir, wenn Sie das alles per Hand selbst erledigen müssten, würde es Ihnen bestimmt keinen Spaß machen!

Der dritte Parameter mit Namen completion ist ein Block mit einem Parameter, der angibt, ob die Animation komplett durchgelaufen ist oder abgebrochen wurde. Sie können diesen Parameter ignorieren, wenn Sie diese Information nicht brauchen.

Beachten Sie, dass Sie sich nicht selbst darum kümmern müssen, wann und wie die Blöcke aufgerufen werden, sondern Sie müssen diese lediglich angeben und UIView kümmert sich um den Rest! Diese Art von Blöcken, die als eigener Datentyp übergeben werden, ist näher im fortgeschrittenen Abschnitt 10.6.1 beschrieben.

16.4.3. Die Animation im Streichholzspiel

/// Entferne eine bestimmte Zahl von Hölzern. 
func entferneHoelzer(anzahl: Int) { 
  // Array mit allen StreichholzView--Instanzen. 
  let alteHoelzer = self.subviews 
 
  // Bestimme die Zahl der zu entfernenden Hölzer. 
  // Sie darf höchstens so groß sein, wie Hölzer noch da sind. 
  let entferneZahl: Int 
  if anzahl > self.sichtbareHoelzer { 
    entferneZahl = self.sichtbareHoelzer 
  } else { 
    entferneZahl = anzahl 
  } 
 
  // Schleife über die Anzahl der zu entfernenden Hölzer. 
  for var i=1; i<=entferneZahl; i+=1 { 
 
    // Index des letzten Holzes, das entfernt werden soll. 
    let letztes = self.sichtbareHoelzer -- i 
    // Die konkrete Instanz, die entfernt werden soll. 
    let altesHolz = alteHoelzer[letztes] 
 
    // Löse die Animation aus. 
    UIView.animateWithDuration(1.0, 
        animations: { () in 
          // Setze die Transparenz am Ende auf 
          // 0, also vollständig durchsichtig. 
          altesHolz.alpha = 0.0 
        }, completion: { (_ : Bool) in 
          // Am Ende entferne das Holz vom 
          // Bildschirm. 
          altesHolz.removeFromSuperview() 
        }) 
  } 
 
  // Reduziere die Anzahl der sichtbaren Hölzer. 
  self.sichtbareHoelzer = self.sichtbareHoelzer -- anzahl 
}

Listing 16.6 Alternative Implementierung der Methode »entferneHoelzer« der Klasse »HaufenView«, die eine kurze Animation benutzt

Nun können Sie die Animation umsetzen. Zunächst lassen Sie die gewünschte Zahl von Hölzern einfach »verschwinden«, indem Sie diese durchsichig darstellen. Dafür gibt es die Eigenschaft alpha, die vor Beginn der Animation auf den Wert 1 eingestellt ist. Innerhalb des ersten Blocks setzen Sie den Wert auf 0 und iOS kümmert sich für Sie um den Rest.

Es gibt aber noch eine Schwierigkeit, um die Sie sich kümmern müssen: Wenn Sie die Animationsmethode aufrufen, so geben Sie die Animationsdauer in Sekunden an. Die Methode ist allerdings sofort beendet, das heißt, Ihr Programm kann in aller Ruhe weiterlaufen, während iOS ohne Ihre Hilfe die Animation durchführt. Dies ist einerseits sehr gut für Sie  Sie haben weniger Arbeit. Andererseits können Sie sich nicht mehr darauf verlassen, dass Sie mit dem alten Aufruf

self.subviews().lastObject()

aus Listing 16.4 in einer Schleife auch wirklich das letzte Streichholz erwischen. Hier müssen Sie leider immer davon ausgehen, dass die Animation noch nicht fertig ist und die ganzen StreichholzView-Objekte in Wahrheit immer noch im Array self.subviews() enthalten sind.

Es kann sogar noch schlimmer kommen: Wenn Sie die Methode entferneHoelzer gleich zweimal hintereinander aufrufen, so ist vielleicht die alte Animation noch nicht fertig und auch die »alten« Streichhölzer des letzten Aufrufs sind noch gar nicht verschwunden!

Deswegen brauchen Sie eine zusätzliche Variable, die Ihnen genau sagt, wie viele Hölzer denn gerade noch übrig sind. Dann können Sie immer diese Zahl benutzen, um festzustellen, an welcher Stelle denn das nächste Holz entfernt werden soll. Zu diesem Zweck fügen Sie eine zusätzliche Eigenschaft

  /// Anzahl der gerade angezeigten Hölzer. 
  var sichtbareHoelzer: Int = 0

zur Klasse HaufenView hinzu. Am Ende der Methode setzeHoelzer auf anzahl:

  // Aktuell sichtbare Zahl von Hölzern. 
  self.sichtbareHoelzer = anzahl

Dann können Sie die Methode entferneHoelzer in der Implementierung neu schreiben, sodass sie nun Animationen benutzt. Dies ist in Listing 16.6 gezeigt.

Fügen Sie einmal eine Zeile in viewDidLoad in der Implementierung der Klasse ViewController ein, die die Animation benutzt:

// Setze eine Anfangszahl von 24 Hölzern. 
haufen.setzeHoelzer(24) 
 
// Entferne 5 davon mit einer Animation. 
haufen.entferneHoelzer(5)

und vergewissern Sie sich, dass die Animation korrekt funktioniert.

Eine weitere, etwas anspruchsvollere Animation erhalten Sie, indem Sie die Hölzer nicht nur einfach verschwinden, sondern sogar umfallen lassen. Dafür können Sie eine Drehung verwenden, indem Sie die Eigenschaft transform geeignet setzen. Probieren Sie es einfach mal aus, indem Sie eine Drehung um einen Viertelkreis wie folgt definieren:

let drehung = CGAffineTransformMakeRotation(CGFloat(M_PI/2))

Da eine Drehung um die Konstante M_PI einer halben Drehung entspricht, erzeugt die Funktion CGAffineTransformMakeRotation mit dem Parameter M_PI/2 genau eine Vierteldrehung. Setzen Sie nun innerhalb des Animationsblocks die transform-Eigenschaft auf diesen Wert:

// Drehung zur "umgefallenen" Position. 
let drehung = CGAffineTransformMakeRotation(CGFloat(M_PI/2)) 
 
// Löse die Animation aus. 
UIView.animateWithDuration(1.0, 
    animations: { () in 
      // Setze die Transparenz am Ende auf 
      // 0, also vollständig durchsichtig. 
      altesHolz.alpha = 0.0 
 
      // Drehe das Holz, sodass es am Ende 
      // der Animation auf der Seite liegt. 
      altesHolz.transform = drehung 
    }, completion: { (_ : Bool) in 
      // Am Ende entferne das Holz vom 
      // Bildschirm. 
      altesHolz.removeFromSuperview() 
    })

und überzeugen Sie sich, dass die Streichhölzer nun alle sichtbar »umfallen«, während sie gleichzeitig verschwinden!

Auf diesem Wege können Sie sehr aufwendige Animationen erzeugen und kombinieren. Es ist sogar möglich, Animationen zu unterschiedlichen Zeiten zu starten und zu beenden, beispielsweise könnten Sie die Streichhölzer schneller umfallen lassen, als sie verschwinden.

16.5 Lang lebe die Streichholz-App

Die Klasse HaufenView und die dazugehörige Hilfsklasse StreichholzView (oder alternativ auch StreichholzViewAlt) können Sie nun in die eigentliche »Streichholz«-App aus Kapitel 15 einbauen. Zunächst erzeugen Sie die zwei Hilfsklassen HaufenView und StreichholzView innerhalb des Projekts und kopieren dann die Inhalte der Dateien mit Copy & Paste aus dem Testprojekt »StreichholzView«. Jetzt können Sie das eigentliche neue View in die App einbauen.

Eine Hürde müssen Sie aber noch nehmen: Der grafische Editor von Xcode kennt zwar verschiedene grafische Elemente wie Slider und Labels, aber er kennt leider nicht Ihr HaufenView. Sie könnten es natürlich im Programmtext jederzeit selbst erzeugen, aber Sie hatten ja bereits den ganzen Rest der grafischen Oberfläche in Xcode erzeugt. Da wäre es schade, wenn Sie die grafische Oberfläche nun selbst erzeugen müssten! (Und Sie müssten auch die Ausrichtungsregeln in Ihrem Programmtext erzeugen. Also lieber nicht so!)

IMG

Abbildung 16.3 Löschen der Ausrichtungsregel »Top Space to: Zug Anzeige« des ersten Buttons

IMG

Abbildung 16.4 Ausrichtungsregel von fester Größe eines »UIView«-Objekts in Xcode

Deswegen bauen Sie das View in Ihre App ein, ohne auf den Komfort des grafischen Editors verzichten zu müssen. Zunächst empfehle ich Ihnen, die drei Buttons »Nimm 1«, »Nimm 2«, und »Nimm 3« nach unten zu ziehen und im fünften Reiter jedes einzelnen Buttons die Ausrichtungsregel TOP SPACE TO: ZUG ANZEIGE zu löschen, siehe Abbildung 16.3.

Dann fügen Sie zunächst ein UIView mithilfe von Xcode in die App ein. Dieses generelle Objekt finden Sie in der Liste als VIEW, es ist sehr weit unten (das vorletzte Objekt in Xcode 7.1). Platzieren Sie es unterhalb des »Zuganzeige«-Labels und setzen Sie die Ausrichtungsregeln:

1. Horizontale Zentrierung und Abstand vom oberen View von 20 Pixeln.
2. Feste Größe mit einer Breite von 300 Pixeln und einer Höhe von 240 Pixeln, siehe Abbildung 16.4.
3. Für jeden der drei Buttons fügen Sie wieder eine neue Regel hinzu, die den Abstand zum darüber liegenden Element (das ist nun das neue UIView) auf 30 Pixel setzt.

Dann ändern Sie im dritten Reiter des neuen UIView in der obersten Gruppe CUSTOM CLASS die CLASS auf »HaufenView«, Abbildung 16.5. Dadurch haben Sie jetzt eine Klasse vom Typ HaufenView in der richtigen Größe eingefügt.

IMG

Abbildung 16.5 Ändern der Klasse des »UIView« auf »HaufenView«

Nun können Sie dieses HaufenView auf dem bekannten Weg mit einer Eigenschaft in ViewController verknüpfen. Beachten Sie, dass die Eigenschaft dabei automatisch die korrekte Klasse HaufenView bekommt. Nennen Sie die Eigenschaft haufenAnzeige:

  /// Grafische Darstellung des Spielhaufens. 
  @IBOutlet weak var haufenAnzeige: HaufenView!

Nun fügen Sie an allen Stellen, an denen im Moment die Streichholzanzahl ins Label streichHolzAnzeige geschrieben wird, die relevanten Methodenaufrufe für die haufenAnzeige ein.

Dies ist im Einzelnen:

1. In der Methode neuesSpiel fügen Sie am Ende ein:
// Anpassen der grafischen Streichholzanzeige. 
self.haufenAnzeige.setzeHoelzer(self.spielHaufen.hoelzer)
2. In der Methode spielerZug fügen Sie am Anfang die Zeile ein:
self.haufenAnzeige.entferneHoelzer(zahl)
3. In derselben Methoden spielerZug fügen Sie in der Zeile unterhalb von
let computerZahl = self.computerStrategie.macheZug()
zusätzlich die Zeile
self.haufenAnzeige.entferneHoelzer(computerZahl)

ein. Dadurch entfernt auch der Computer bei seinem Zug die korrekte Anzahl von Streichhölzern.

Starten Sie nun die App neu und vergewissern Sie sich, dass die Streichhölzer korrekt grafisch angezeigt werden und mit einer kleinen Animation verschwinden, sobald Sie oder der Computer einen Zug machen. Vergewissern Sie sich ebenfalls, dass das Spiel noch genauso richtig funktioniert wie vorher!

Abbildung 16.6 zeigt ein paar Beispiele des Verlaufs des Streichholzspiels. Ich wünsche Ihnen viel Spaß dabei!

IMG

Abbildung 16.6 Ein paar Beispiele aus dem Streichholzspiel

16.6 Aufgaben

1. Ändern Sie die Methode entferneHoelzer in Listing 16.6 so, dass die Animation für das Verschwinden halb so schnell passiert wie die Animation für das Umfallen.
2. Ändern Sie die Methode entferneHoelzer in Listing 16.6 so, dass das Verschwinden erst dann gestartet wird, nachdem die Hölzer umgefallen sind.

Hinweis: Sie können Aufrufe in Blöcken ineinanderschachteln!

3. Ändern Sie die Methode entferneHoelzer in Listing 16.6 so, dass die Animationen unterschiedlich lange dauern: Und zwar soll eine Animation zwischen 0,5 und 1,5 Sekunden dauern und die Dauer soll durch eine Zufallszahl erzeugt werden.
4. Schreiben Sie eine alternative Implementierung für das Verschwinden der Streichhölzer, bei der ein oranger Kreis erscheint (der eine Flamme darstellen soll) und jedes Streichholz von oben bis unten »abbrennt«.

Achtung: Diese Aufgabe ist recht anspruchsvoll!