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

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Grundlagen
3 Views und Viewcontroller
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Jahrmarkt der Nützlichkeiten
A Sicherer Entwicklungszyklus
Stichwort

Download:
- ZIP, ca. 49,9 MB
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
Galileo Computing
1000 S., geb., mit DVD
39,90 Euro, ISBN 978-3-8362-1915-0
Pfeil 3 Views und Viewcontroller
Pfeil 3.1 Eigene View-Klassen in Cocoa Touch
Pfeil 3.1.1 Zeichnen in Cocoa Touch
Pfeil 3.1.2 Zeitberechnung
Pfeil 3.1.3 View-Erzeugung über NIB-Dateien
Pfeil 3.1.4 Aktualisierung der Zeitanzeige
Pfeil 3.1.5 Wiederverwendbarkeit von Views
Pfeil 3.2 Views und Viewcontroller
Pfeil 3.2.1 Outlets
Pfeil 3.2.2 Outlet-Collections
Pfeil 3.2.3 Containerviews
Pfeil 3.2.4 Viewhierarchien
Pfeil 3.2.5 Actions
Pfeil 3.2.6 Ereignisse
Pfeil 3.2.7 Controlzustände und Buttons
Pfeil 3.2.8 Direkte Gestenverarbeitung
Pfeil 3.2.9 Übergänge
Pfeil 3.2.10 Der Lebenszyklus eines Viewcontrollers
Pfeil 3.2.11 Speicher- und Ressourcenverwaltung des Viewcontrollers
Pfeil 3.3 Lokale Benachrichtigungen
Pfeil 3.3.1 Benachrichtigungen versenden
Pfeil 3.3.2 Benachrichtigungen verarbeiten
Pfeil 3.4 Eine App für alle
Pfeil 3.4.1 Das Retina-Display
Pfeil 3.4.2 Sprachkursus für die App
Pfeil 3.4.3 Es funktioniert nicht
Pfeil 3.4.4 Launch-Images und iPhone 5
Pfeil 3.4.5 Universelle Apps
Pfeil 3.5 Fehlersuche
Pfeil 3.5.1 Logging
Pfeil 3.5.2 Der Debugger
Pfeil 3.5.3 Die Debuggerkonsole
Pfeil 3.5.4 Laufzeitfehler

3 Views und ViewcontrollerZur nächsten Überschrift

»Never put off till tomorrow what you can do the day after tomorrow.«
– Mark Twain

In diesem Kapitel werden Sie einen analogen Wecker programmieren und dabei den grundlegenden Aufbau von iOS-Apps kennenlernen. Dabei stehen die Arbeit mit Xcode und die praktische Anwendung der theoretischen Grundlagen im Vordergrund, die wir in den ersten beiden Kapiteln beschrieben haben. Außerdem gestalten Sie eine einfache eigene Oberfläche, die sowohl eigene Grafiken auf den Bildschirm zeichnet als auch Nutzereingaben verarbeitet. Denn das wichtigste Merkmal eines analogen Weckers ist schließlich das Ziffernblatt mit den Zeigern.

Projektinformation

Den Quellcode des folgenden Beispiels finden Sie auf der DVD unter Code/Apps/AlarmClock oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/AlarmClock. Außerdem finden Sie einige der besprochenen Varianten des Projekts unter Code/Apps/Clock beziehungsweise https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/Clock.


Galileo Computing - Zum Seitenanfang

3.1 Eigene View-Klassen in Cocoa TouchZur nächsten ÜberschriftZur vorigen Überschrift

Und los geht‘s: Legen Sie ein neues iPhone-Projekt in Xcode aus der Vorlage Single View Application an (siehe Abbildung 3.1), und geben Sie dem Projekt den Namen AlarmClock.

Abbildung

Abbildung 3.1 Auswahl der Projektvorlage für den Wecker

Im zweiten Schritt schalten Sie alle Checkboxen aus. Der Code in diesem Kapitel verwendet manuelles Referenzenzählen, um die Speicherverwaltungsregeln zu vertiefen. Das Beispielprojekt basiert außerdem auf XIB-Dateien, da Sie Storyboards erst ab iOS 5 einsetzen können. Sie können aber fast alle Beschreibungen für XIB-Dateien auch auf Storyboards übertragen. Als Produktnamen und Präfix für die Klassennamen geben Sie »AlarmClock« ein (siehe Abbildung 3.2). Danach speichern Sie das Projekt in einem Verzeichnis Ihrer Wahl ab.

Abbildung

Abbildung 3.2 Die Parameter für das neue Projekt

Als Erstes soll der Wecker ein Ziffernblatt erhalten. Da es dafür keinen fertigen View in Cocoa Touch gibt, müssen Sie eine eigene Viewklasse erstellen. Zum Anlegen der Klasse klicken Sie mit der rechten Maustaste auf die Gruppe AlarmClock und wählen den Punkt New File... aus (siehe Abbildung 3.3).

Abbildung

Abbildung 3.3 Hinzufügen einer neuen Klasse

Wählen Sie in der linken Spalte den Punkt Cocoa Touch in der Rubrik iOS aus. Auf der rechten Seite erscheint eine Liste mit Dateivorlagen, aus der Sie Objective-C class auswählen. Geben Sie im nächsten Schritt der Klasse den Namen ClockView, und wählen Sie als Superklasse UIView aus (siehe Abbildung 3.4) – Xcode erstellt die Klassen dann als Subklasse von UIView.

Abbildung

Abbildung 3.4 Subklasse von »UIView«

Im letzten Schritt wählen Sie den Ordner, die Gruppe und das Target für die Header- und die Implementierungsdatei aus (siehe Abbildung 3.5). Hier können Sie einfach die Voreinstellungen übernehmen.

Gruppen und Targets

Dabei ist eine Gruppe ist jeweils eine Hierarchieebene in der Dateiansicht von Xcode. Obwohl Gruppen ein gelbes Ordnersymbol als Icon haben, entsprechen sie keinem Dateiordner auf der Festplatte. Mit einem Xcode-Projekt können Sie unterschiedliche Produkte (z. B. Programme) erzeugen. Wie Xcode dieses Produkt erstellt, beschreibt dabei ein Target.

Abbildung

Abbildung 3.5 Anlegen der neuen Klasse in der Gruppe »AlarmClock«

Nach dem Anlegen der Dateien zeigt ein Blick in den Quelltext der Headerdatei der neuen Klasse, dass Xcode eine Subklasse von UIView erstellt hat:

#import <UIKit/UIKit.h>

@interface ClockView : UIView

@end

Listing 3.1 Header-Datei der Klasse »ClockView«

Bevor Sie jedoch die Zeichenlogik für das Ziffernblatt implementieren, erstellen Sie die Oberfläche der App. Dann können Sie das Programm immer wieder starten und sich so die Auswirkungen Ihrer Änderungen ansehen.

Um die neue Viewklasse benutzen zu können, müssen Sie einen View in der XIB-Datei des Viewcontrollers anlegen, die Xcode beim Anlegen des Projekts erzeugt hat. Wählen Sie im Projektnavigator die Datei AlarmClockViewController.xib aus. Xcode öffnet dann den Interface Builder und zeigt den enthaltenen View an. Um das Ziffernblatt in diesen View einzufügen, ziehen Sie einen UIView aus der Objektbibliothek auf den bereits vorhandenen View (siehe Abbildung 3.6).

Abbildung

Abbildung 3.6 Einen neuen View im Interface Builder anlegen

Der neue View hat automatisch dieselbe Größe wie der bereits vorhandene erhalten. Da das Ziffernblatt des Weckers aber nicht den ganzen Bildschirm ausfüllen soll, markieren Sie den gerade hinzugefügten View, indem Sie ihn anklicken, und öffnen rechts oben im Xcode-Fenster den Größeninspektor (siehe den Mauszeiger in Abbildung 3.7; alternativ drücken Sie alt + cmd + 5).

Der Größeninspektor

In Xcode bis Version 4.4 einschließlich zeigt der Größeninspektor unterhalb der Größeneinstellungen das Autosizing anstatt der Autolayouteinstellungen an. In Xcode 4.5 können Sie zwischen Autosizing und Autolayouts im Dateiinspektor der XIB-Datei wechseln; diese Einstellung ist allerdings beim AlarmClock-Beispielprojekt noch irrelevant. Mehr zu diesem Thema finden Sie in Kapitel 5, »Daten, Tabellen und Controller«.

Setzen Sie die Koordinaten und die Größe des Views so, wie in Abbildung 3.7 dargestellt. Er sollte eine quadratische Grundfläche von 320 × 320 Punkten haben und am Koordinatenursprung (0, 0) liegen. Der Ursprung eines Views ist immer die linke obere Ecke. Die horizontale Ausdehnung wächst nach rechts und die vertikale nach unten.

Abbildung

Abbildung 3.7 Der Größeninspektor des Views mit Autolayout

Das Ziffernblatt belegt mit dieser Einstellung nur circa zwei Drittel der oberen Fläche des iPhone-Bildschirms. Sie können das überprüfen, indem Sie das Projekt ausführen. Der Simulator zeigt jetzt eine weiße und darunter eine etwa halb so große graue Fläche an.

Abbildung

Abbildung 3.8 Der Größeninspektor mit Autosizing

Über das dritte Symbol von links (neben dem Größeninspektor) oder mit alt + cmd + 3 wählen Sie den Identitätsinspektor aus. Legen Sie dort über das oberste Eingabefeld, Class, die Klasse des Views fest. Sie können den Klassennamen dort entweder direkt eingeben oder aus einer Liste auswählen (siehe Abbildung 3.9). Weisen Sie dem View die neu angelegte Klasse ClockView zu.

Abbildung

Abbildung 3.9 Der View bekommt die Klasse »ClockView« zugewiesen.

Der Interface Builder kennt die Klassenhierarchie

Beim Erzeugen des Views haben Sie einen UIView verwendet. Damit haben Sie bereits festgelegt, dass als Klasse für diesen View nur UIView oder eine ihrer Unterklassen infrage kommt. Daher erscheinen nur genau diese Klassennamen in der Liste. Pfiffigerweise haben Sie ja die designierte Klasse ClockView als Unterklasse von UIView deklariert, und so kommt sie in der Liste vor, und alles passt.

Die Implementierungsdatei der Klasse ClockView enthält bereits zwei Methoden. Die erste Methode heißt initWithFrame: und hat einen Parameter vom Typ CGRect. Das ist ein zusammengesetzter Datentyp [Ein C-Struct] und keine Objective-C-Klasse, der ein Rechteck über eine Koordinate und Seitenlängen (die im Folgenden auch »Größe« genannt werden) darstellt.

Dieser Typ hat Klasse

Sie können einfache Datentypen und C-Strukturen in Cocoa nicht am Namen von Klassen unterscheiden. Zum Beispiel gibt es eine Klasse NSNumber und einen einfachen Datentyp NSInteger. Es gibt darauf jedoch keinen Hinweis im Namen.

Objective-C erlaubt nur Zeiger als Verweise auf Objekte, weswegen die Deklaration NSNumber theNumber; [ [In der Deklaration fehlt der Stern zwischen dem Klassennamen und der Variable.] zu der Fehlermeldung

interface type cannot be statically allocated

führt. Umgekehrt dürfen Sie dagegen Zeiger auf einfache Datentypen deklarieren.

Die Deklaration NSInteger *theInteger; ist erlaubt, und erst bei der Zuweisung erhalten Sie in der Regel eine Warnung. Falls Sie sich nicht sicher sind, was ein Name im Quelltext beschreibt, können Sie durch cmd + linke Maustaste zur Deklaration dieses Namens gelangen. Das funktioniert praktischerweise mit fast allen Bezeichnern im Quelltext. Bei Bezeichnern aus Cocoa können Sie auch alt + linke Maustaste drücken, um eine kurze Dokumentation zu erhalten.

In Objective-C gibt es keine Konstruktoren, die Sie vielleicht aus anderen Programmiersprachen kennen. Die Objekterzeugung ist vielmehr in Speicherplatzreservierung und Objektinitialisierung aufgeteilt. Die Klassenmethode alloc zur Speicherplatzreservierung haben Sie bereits kennengelernt. Sie reserviert den notwendigen Speicherplatz und füllt ihn mit Nullen.

Initialisierungsmethoden kennen Sie ebenfalls schon seit dem zweiten Kapitel. In Objective-C heißen sie per Konvention entweder init oder haben das Präfix init. Wenn Sie einen View über den Programmcode anlegen, sollten Sie in der Regel die Initialisierungsmethode initWithFrame: verwenden. Der Parameter ist ein Rechteck, das die Größe und Position des neuen Views relativ zu dessen umgebendem View, dem Superview, angibt. Da Cocoa Touch die Views in einer Hierarchie anordnet, hat fast jeder View einen umgebenden View; das ist jeweils dessen Vorgänger in der Hierarchie. Der Aufbau der Methode ist im Allgemeinen wie folgt:

- (id)initWithFrame:(CGRect)inFrame {
self = [super initWithFrame:inFrame];
if(self != nil) {
// Initialisierung des Objekts
}
return self;
}

Listing 3.2 Initialisierungsmethode eines Views

Viewgeometrie

Die Klasse UIView besitzt zwei Rechtecke, die die Position und die Ausmaße des Views beschreiben. Die Property frame enthält, wie bereits beschrieben, die Position und die Größe des Views relativ zu seinem umgebenden View. Die Position des Frames gibt also die Entfernung der oberen linken Ecke des Views von der oberen linken Ecke seines umgebenden Views an (siehe Abbildung 3.10, links).

Der gezeichnete View muss hingegen nicht auf dem Rechteck liegen, das der Frame beschreibt. Diese Koordinaten gibt das Rechteck in der Property bounds an.

Die Bounds beschreiben also die Lage des Views relativ zu dessen Frame. In den meisten Fällen ist die Position der Bounds der Nullpunkt, und die Größe entspricht der Größe des Frames (Abbildung 3.10, rechts).

Das klingt recht kompliziert. Für den Anfang können Sie sich zwei einfache Faustregeln merken:

1. Sie verändern die Größe und die Position eines Views in der Regel über dessen Frame.

2. Wenn Sie die Koordinaten eines Views relativ zu dessen umgebendem View berechnen möchten, verwenden Sie die Bounds des umgebenden Views, um den Frame des enthaltenen Views zu berechnen.

Das wird später noch an ein paar Beispielen erläutert.

Abbildung

Abbildung 3.10 Rechteck für Frame und Bounds

Die Methode in Listing 3.2 ruft als Erstes die Initialisierungsmethode der Oberklasse auf und weist deren Ergebnis der Variablen self zu. Danach überprüft sie, ob self nicht nil ist. Wenn self auf ein Objekt zeigt, kann sie das Objekt initialisieren und schließlich auch zurückgeben.

Vielleicht überrascht es Sie, dass die Initialisierungsmethode self verändern kann oder nil zurückliefern darf. Das ist indes keine theoretische Möglichkeit, sondern wird auch eingesetzt. Eine Initialisierungsmethode sollte beispielsweise nil zurückgeben, wenn sie das Objekt nicht initialisieren kann. Die Initialisierungsmethode initWithString: der Klasse NSURL gibt beispielsweise nil zurück, wenn die angegebene URL einen syntaktischen Fehler enthält.

Die Klasse ClockView enthält in einem Kommentarblock eine weitere Methode namens drawRect:. Entfernen Sie die Kommentarzeichen um diese Methode, da Sie sie im Folgenden überschreiben werden. Cocoa Touch ruft sie automatisch auf, wenn es den Inhalt des Views neu zeichnen muss.


Galileo Computing - Zum Seitenanfang

3.1.1 Zeichnen in Cocoa TouchZur nächsten ÜberschriftZur vorigen Überschrift

Als Nächstes erstellen Sie den Code, um alle Bestandteile der Uhr – also Ziffernblatt und die Uhrzeiger – zu zeichnen. Dafür verwenden Sie das CoreGraphics-Framework. CoreGraphics ist ein sehr mächtiges Framework, das auf reinem C und nicht auf Objective-C basiert. Deshalb enthält es keine Klassen und Methoden, sondern Datentypen, Strukturen und Funktionen. Alle zum CoreGraphics-Framework gehörenden Elemente erkennen Sie am Präfix CG.

Der Aufbau und die Implementierung der Datentypen in den Frameworks sind vor dem Nutzer versteckt. Sie können auf die Datentypen nur über Referenzen zugreifen. Die Referenzen übergeben Sie an Funktionen, die eine bestimmte Operation ausführen. Auf diese Art implementierte Datentypen nennt man auch opake Datentypen.

Der zentrale Datentyp in CoreGraphics ist der Grafikkontext, und die Referenzen darauf haben den Typ CGContextRef. Ein Grafikkontext speichert alle Werte für den aktuellen Zustand Ihrer Zeichenoperationen. Wenn Sie eine Zeichenoperation über einen Grafikkontext ausführen möchten, müssen Sie also zuerst die Parameter wie Farben, Muster und Clipping festlegen und danach die gewünschte Operation aufrufen. Für das Ziffernblatt werden Sie zunächst einen weißen Kreis als Grundfläche zeichnen. Die entsprechenden CoreGraphics-Operationen dazu sehen wie folgt aus:

- (void)drawRect:(CGRect)inRectangle {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect theBounds = self.bounds;
CGContextSaveGState(theContext);
CGContextSetRGBFillColor(theContext, 1.0, 1.0, 1.0, 1.0);
CGContextAddEllipseInRect(theContext, theBounds);
CGContextFillPath(theContext);
CGContextRestoreGState(theContext);
}

Listing 3.3 Zeichnen einer weißen Ellipse

Das Rechteck, das die Methode drawRect: als Parameter erhält, ist nicht die Zeichenfläche, sondern nur der Bereich der Zeichenfläche, den die Methode neu zeichnen soll. Diesen Wert verwenden Sie allerdings in den meisten Fällen nicht, da dieses Rechteck nur für eine Reduktion der Zeichenoperationen bei optimierter Ausgabe interessant ist. Das ist in der Regel jedoch nur bei sehr großen oder aufwendig zu zeichnenden Views notwendig.

In der ersten Anweisung weist die Methode der Variablen theContext über die Funktion UIGraphicsGetCurrentContext den aktuellen Grafikkontext zu, in den die Ausgabe erfolgt. Cocoa Touch erzeugt den Kontext vor dem Aufruf der drawRect:-Methode, und Sie dürfen drawRect: daher niemals direkt aufrufen, da dann kein Kontext zur Verfügung steht. Wenn Sie einen View neu zeichnen lassen möchten, sollten Sie stattdessen die Methode setNeedsDisplay des Views aufrufen. Cocoa Touch zeichnet den View dann neu, sobald das möglich ist.

Da die Größe des Ziffernblattes von der Größe des Views abhängt, ermittelt die Methode die Koordinaten und die Größe des Views in der Variablen theBounds. Die Koordinaten des Ziffernblattes liegen relativ zu den Koordinaten des Views, deshalb müssen Sie hier die Bounds und nicht den Frame für die Berechnung verwenden.

In der nächsten Zeile sichern Sie den aktuellen Zustand des Grafikkontexts über die Funktion CGContextSaveGState. Damit lässt sich am Ende der Methode genau dieser Zustand sehr einfach über die Funktion CGContextRestoreGState wiederherstellen. Da Sie nicht wissen, welche Zeichenoperationen in dem Grafikkontext nach der Ausführung Ihrer drawRect:-Methode noch ausgeführt werden, sollten Sie immer darauf achten, den Kontext so zu hinterlassen, wie Sie ihn vorgefunden haben. (Ganz anders als die Toiletten der Deutschen Bahn, die man ja so hinterlassen soll, wie man sie vorzufinden wünscht. Dummerweise hat man nie einen Dampfstrahler dabei, wenn man ihn braucht.) Achten Sie bei der Verwendung dieser beiden Funktionen aber bitte immer darauf, ihre Aufrufe zu balancieren; das heißt, auf jeden CGContextSaveGState-Aufruf sollte immer genau ein CGContextRestoreGState-Aufruf folgen.

Die folgenden Zeilen zeichnen dann den Kreis des Ziffernblattes, indem die Methode zuerst die Füllfarbe als RGBA-Werte über die Funktion CGContextSetRGBAFillColor setzt. Die Fließkommawerte für die Farbkomponenten (Rot, Grün und Blau) und den Alphawert, der die Deckkraft festlegt, dürfen dabei zwischen 0 und 1 liegen. Die Funktion CGContextAddEllipseInRect fügt dem Kontext einen Pfad in Form einer Ellipse hinzu. Bei einem quadratischen Ziffernblatt haben die beiden Radien dieser Ellipse die gleiche Länge, und es entsteht ein Spezialfall der Ellipse – ein Kreis. Durch den Aufruf der Funktion CGContextFillPath zeichnet schließlich CoreGraphics einen ausgefüllten Kreis mit der gesetzten Füllfarbe.

Pfade in CoreGraphics

Sie zeichnen Linien und Flächen in CoreGraphics, indem Sie im Grafikkontext zuerst den Linienzug der Figur beschreiben. Danach rufen Sie eine Zeichenoperation für den Pfad auf, mit der Sie den Linienzug über CGContextStrokePath zeichnen oder ihn über CGContextFillPath ausfüllen. Eine weitere mögliche Operation ist das Clippen über die Funktion CGContextClip. Dabei beschneiden Sie die Zeichenfläche, sodass der Kontext nur noch im Bereich des Linienzuges zeichnet.

Nach der Ausführung der Zeichenoperation vergisst der Kontext übrigens den Pfad. Wenn Sie also einen ausgefüllten Kreis mit einem Rand zeichnen möchten, müssen Sie den Pfad des Kreises jeweils vor den beiden Zeichenoperationen zu dem Kontext hinzufügen.

Wenn Sie Ihr Programm ausführen, werden Sie feststellen, dass immer noch ein weißes Quadrat und ein graues Rechteck angezeigt werden. Das liegt daran, dass der View für das Ziffernblatt ebenfalls Weiß als Hintergrundfarbe verwendet. Er stellt also einen weißen Kreis vor einem weißen Hintergrund dar.

Damit Sie den Kreis sehen können, ändern Sie die Hintergrundfarbe des Views. Öffnen Sie dazu den View im Interface Builder, und wählen Sie den View für das Ziffernblatt aus, indem Sie auf die entsprechende Stelle des Hauptviews klicken. Mit dem Attributinspektor, der zwischen dem Identitäts- und dem Größeninspektor liegt, können Sie über das Attribut Background die Hintergrundfarbe des Views verändern (siehe Abbildung 3.11). Wählen Sie ein freundliches Grau oder die Transparenz (Clear Color) aus. Bei einem transparenten Hintergrund erscheint an den Stellen, die der Kreis nicht verdeckt, die Hintergrundfarbe des umgebenden Views.

Abbildung

Abbildung 3.11 Der Subview bekommt eine eigene Farbe.

Die Änderung lässt sich umgehend im Simulator überprüfen – das Ziffernblatt ist jetzt sichtbar (siehe Abbildung 3.12) – ein schöner runder Kreis.

Abbildung

Abbildung 3.12 Das Ziffernblatt ist fast fertig.

Das Ziffernblatt ist in diesem Stadium noch sehr minimalistisch. Im nächsten Schritt fügen Sie die Zeichenoperationen für die Darstellung der Stunden- und Minuteneinteilung hinzu. Dazu braucht die Klasse ClockView zwei neue Methoden, die Sie vor der drawRect:-Methode einfügen:

- (CGPoint)midPoint {
CGRect theBounds = self.bounds;
return CGPointMake(CGRectGetMidX(theBounds),
CGRectGetMidY(theBounds));
}

Listing 3.4 Berechnung des Mittelpunktes des Views

Die Methode midPoint berechnet den Mittelpunkt des Views relativ zum bounds-Rechteck. Die Klasse UIView hat zwar eine ähnliche Methode center, die allerdings den Mittelpunkt des frame-Rechtecks liefert. Sie ist hierfür jedoch nicht geeignet, da Sie ja das Ziffernblatt relativ zum View zeichnen müssen.

Mathematik für Einsteiger

Sie werden sich möglicherweise fragen, was die viele Mathematik im ersten größeren Beispiel soll. Nun, die Antwort ist einfach. Mathematik begegnet einem Programmierer häufiger, als ihm bisweilen lieb ist. Gerade bei der Erstellung von Frontends und der Arbeit mit Grafiken und Animationen ist in der Regel ein gutes mathematisches Grundverständnis gefragt. Die im Wecker-Beispiel angewandte Mathematik ist ja kein Selbstzweck, sondern resultiert direkt aus der Verwendung des CoreGraphics-Frameworks. Eine kleine Auffrischung Ihres Wissens über Bogenmaß, Winkelberechnung etc. finden Sie bei Wikipedia [https://secure.wikimedia.org/wikipedia/de/wiki/Bogenmass].

Die zweite Methode, pointWithRadius:angle:, berechnet einen Punkt, der vom Mittelpunkt eine gegebene Entfernung hat und um einen gegebenen Winkel gedreht ist. Die Winkel werden im Bogenmaß, im Uhrzeigersinn und ausgehend von 12 Uhr gerechnet. Einen Winkel w in Grad können Sie über die Formel wp / 180° ins Bogenmaß umrechnen.

- (CGPoint)pointWithRadius:(CGFloat)inRadius
angle:(CGFloat)inAngle {
CGPoint theCenter = [self midPoint];
return CGPointMake(theCenter.x + inRadius * sin(inAngle),
theCenter.y – inRadius * cos(inAngle));
}

Listing 3.5 Berechnung eines Punktes über einen Radius und einen Winkel

Das Ziffernblatt stellt einen halben Tag, also 12 Stunden, dar. Somit entsprechen 12 Stunden einem vollen Kreis von 360°, und eine Stunde entspricht

360° / 12 = 30°

oder

30° p / 180° = p / 6

im Bogenmaß. Eine Stunde hat 60 Minuten, und damit hat eine Minute einen Winkel von

360° / 60 = 6°

oder

p / 180° = p / 30

im Bogenmaß. Für eine Sekunde gelten natürlich die gleichen Winkelwerte wie für Minuten.

Beispielsweise hat ein Zeiger auf »drei Uhr« den Winkel 90° beziehungsweise p / 2 im Bogenmaß oder auf »10 Minuten« den Winkel 60° beziehungsweise p / 3. Die Position eines Uhrzeigers oder eines Strichs auf dem Ziffernblatt kann also durch die Angabe eines Winkels und ein oder mehrere Längenangaben bestimmt werden. Diese Beispiele stellt Abbildung 3.13 grafisch dar.

Abbildung

Abbildung 3.13 Beziehung zwischen Uhrzeiten und Winkeln

Das Ziffernblatt soll Minuten durch einen Punkt und Stunden durch einen Strich darstellen. Die Striche für drei, sechs, neun und zwölf Uhr sollen dabei etwas länger als die sonstigen Stundenstriche sein. Die Einteilungen werden über eine Schleife gezeichnet, die von 0 bis 59 läuft; für jede Minute ist jeweils ein Durchlauf vorgesehen.

Da alle Einteilungen die gleiche Farbe, Linienstärke und Linienenden haben sollen, können Sie diese Einstellungen vor der Schleife festlegen. Beachten Sie, dass die Stunden-Einteilungen Linien und die Minuten-Einteilungen Kreise sind. Die Linienfarbe müssen Sie über CGContextSetRGBStrokeColor setzen. Die Stundenstriche sollen außerdem abgerundete Linienenden haben, was Sie über die Funktion CGContextSetLineCap festlegen können. Da diese runden Kappen jedoch auf beiden Enden der Linien sitzen, reichen sie über den Rand des Ziffernblattes hinaus. Sie können das durch ein Clipping verhindern. Das ist eine unsichtbare Maske auf der Zeichenfläche, mit der Sie die Zeichenfläche in Bereiche unterteilen können. Der Grafikkontext zeichnet dann nur in den Bereichen, die zum Clipping gehören. Als Clipping-Region verwenden Sie den Kreis des Ziffernblattes. Sie fügen dessen Pfad erneut dem Kontext hinzu und setzen diesen in der Methode drawRect: durch Aufruf von CGContextClip als Clipping-Pfad. Listing 3.6 enthält die soeben beschriebenen Funktionsaufrufe für die Kontexteinstellungen:

CGContextSetRGBStrokeColor(theContext, 0.25, 0.25, 0.25, 1.0);
CGContextSetRGBFillColor(theContext, 0.25, 0.25, 0.25, 1.0);
CGContextSetLineWidth(theContext, 7.0);
CGContextSetLineCap(theContext, kCGLineCapRound);
CGContextAddEllipseInRect(theContext, theBounds);
CGContextClip(theContext);

Listing 3.6 Setzen von Zeichenattributen im Grafikkontext

Die Schleife zum Zeichnen der Einteilungen sieht nun folgendermaßen aus:

CGFloat theRadius = CGRectGetWidth(theBounds) / 2.0;
for(NSInteger i = 0; i < 60; ++i) {
CGFloat theAngle = i * M_PI / 30.0; // eine Minute
if(i % 5 == 0) { // alle 5 Minuten ein Strich
CGFloat theInnerRadius = theRadius *
(i % 15 == 0 ? 0.7 : 0.8);
CGPoint theInnerPoint =
[self pointWithRadius:theInnerRadius
angle:theAngle];
CGPoint theOuterPoint = [self
pointWithRadius:theRadius
angle:theAngle];
CGContextMoveToPoint(theContext, theInnerPoint.x,
theInnerPoint.y);
CGContextAddLineToPoint(theContext, theOuterPoint.x,
theOuterPoint.y);
CGContextStrokePath(theContext);
}
else {
CGPoint thePoint =
[self pointWithRadius:theRadius * 0.95
angle:theAngle];
CGContextAddArc(theContext, thePoint.x, thePoint.y,
3.0, 0.0, 2 * M_PI, YES);
CGContextFillPath(theContext);
}
}

Listing 3.7 Zeichnen eines Ziffernblattes

Anhand der Schleifenvariablen i können Sie entscheiden, um welchen Einteilungstyp es sich handelt. Wenn ihr Wert durch 5 teilbar ist (i % 5 == 0), muss es ein Strich sein. Ist sie außerdem auch durch 15 teilbar (i % 15 == 0), so muss es ein langer Strich sein. Alle anderen Werte sind Punkte. Die Länge der Striche und die Lage der Punkte errechnen sich relativ zum Radius des Ziffernblattes.

Um die Linien jeweils am gewünschten Punkt beginnen zu lassen, müssen Sie den Startpunkt der Linie setzen, ohne den Pfad des Kontexts zu erweitern. Dazu verwenden Sie die Funktion CGContextMoveToPoint.

Teilbarkeit

Ob eine ganze Zahl ein Teiler einer anderen Zahl ist, können Sie über den Divisionsrestoperator % ermitteln. Wenn der Rest einer Division 0 ist, dann ist der Dividend durch den Divisor teilbar. Beispielsweise prüft der Ausdruck i % 5 == 0, ob der Dividend i durch den Divisor 5 teilbar ist.

Zum Zeichnen der Punkte verwendet der Code die Funktion CGContextAddArc. Es ist hier einfacher, den Kreis über dessen Mittelpunkt und Radius zu bestimmen als über sein umgebendes Rechteck. Über diese Funktion können Sie beliebige Kreisbögen erzeugen, weswegen Sie den Start- und den Endwinkel des Bogens im Bogenmaß angeben müssen. Damit Sie prüfen können, ob Sie auf dem richtigen Weg sind, sehen Sie hier noch einmal die ganze Methode drawRect: an einem Stück, und die Ausgabe sehen Sie in Abbildung 3.14.

- (void)drawRect:(CGRect)inRectangle {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect theBounds = self.bounds;
CGFloat theRadius = CGRectGetWidth(theBounds) / 2.0;

CGContextSaveGState(theContext);
CGContextSetRGBFillColor(theContext, 1.0, 1.0, 1.0, 1.0);
CGContextAddEllipseInRect(theContext, theBounds);
CGContextFillPath(theContext);
CGContextAddEllipseInRect(theContext, theBounds);
CGContextClip(theContext);
CGContextSetRGBStrokeColor(theContext, Zeilenumbruch
0.25, 0.25, 0.25, 1.0);
CGContextSetRGBFillColor(theContext, Zeilenumbruch
0.25, 0.25, 0.25, 1.0);
CGContextSetLineWidth(theContext, 7.0);
CGContextSetLineCap(theContext, kCGLineCapRound);
for(NSInteger i = 0; i < 60; ++i) {
CGFloat theAngle = i * M_PI / 30.0;

if(i % 5 == 0) {
CGFloat theInnerRadius = theRadius * Zeilenumbruch
(i % 15 == 0 ? 0.7 : 0.8);
CGPoint theInnerPoint = Zeilenumbruch
[self pointWithRadius:theInnerRadius Zeilenumbruch
angle:theAngle];
CGPoint theOuterPoint = Zeilenumbruch
[self pointWithRadius:theRadius Zeilenumbruch
angle:theAngle];
CGContextMoveToPoint(theContext, theInnerPoint.x, Zeilenumbruch
theInnerPoint.y);
CGContextAddLineToPoint(theContext, Zeilenumbruch
theOuterPoint.x,
theOuterPoint.y);
CGContextStrokePath(theContext);
}
else {
CGPoint thePoint =
[self pointWithRadius:theRadius * 0.95 Zeilenumbruch
angle:theAngle];
CGContextAddArc(theContext,thePoint.x, thePoint.y,Zeilenumbruch
3.0, 0.0, 2 * M_PI, YES);
CGContextFillPath(theContext);
}
}
[self drawClockHands];
CGContextRestoreGState(theContext);
}

Listing 3.8 Die komplette Methode zum Zeichnen des Ziffernblattes

Abbildung

Abbildung 3.14 Das Ziffernblatt des Weckers

Die Methode drawClockHands, deren Aufruf am Ende von drawRect: erfolgt, zeichnet die Zeiger des Weckers. Für die Implementierung müssen Sie die aktuelle Systemzeit in Winkel umrechnen, damit Sie die Zeiger durch Linien darstellen können.


Galileo Computing - Zum Seitenanfang

3.1.2 ZeitberechnungZur nächsten ÜberschriftZur vorigen Überschrift

Der ClockView soll die angezeigte Zeit in der Property time speichern. Diese hat den Typ NSDate, dessen Objekte einen Zeitpunkt enthalten, der auf einem Referenzzeitpunkt basiert. Da die Property das Datumsobjekt halten muss, bekommt sie den Speicherverwaltungstyp retain. Das Datumsobjekt enthält den Zeitpunkt als Anzahl der Sekunden zu einem festgelegten Referenzzeitpunkt – dem 1. Januar 2001 in der Zeitzone Greenwich Mean Time (GMT). Hingegen ist die Umrechnung des Zeitpunktes in eine Datumsdarstellung sehr schwer. Dafür gibt jedoch es glücklicherweise spezielle Klassen. Diese Darstellung von Datumswerten klingt zunächst unnötig kompliziert, hat indes einige Vorteile:

  • Sie können mit Datumsobjekten gut rechnen. Beispielsweise können Sie einen weiteren Zeitpunkt durch Addition eines positiven oder negativen Zeitintervalls berechnen.
  • Der Wert eines Zeitpunktes hat überall auf der Welt und zu jeder Zeit die gleiche Bedeutung.
  • Es gibt sehr viele unterschiedliche Zeitzonen und Kalender, wie beispielsweise den buddhistischen, gregorianischen und japanischen Kalender. Es gibt also sehr viele verschiedene Darstellungen des gleichen Zeitpunktes. Es ist einfacher, einen Zeitpunkt in eine Datumsdarstellung umzuwandeln als eine Datumsdarstellung in eine andere.
  • Die internen Uhren aller Computer – auch die der iPhones und iPads – und der Atomuhren stellen die aktuelle Zeit als Differenz zu einem Referenzzeitpunkt dar. Für die Erzeugung eines Datumsobjekts braucht der Computer also nicht viel Rechenzeit.

Um einen Zeitpunkt in eine Datumsdarstellung umzurechnen, gibt es zwei Klassen im Foundation-Framework:

  • NSDateFormatter
  • NSCalendar

Mit NSDateFormatter können Sie zu einem NSDate-Objekt eine Zeichenkette erzeugen, die das enthaltene Datum darstellt. Diese Klasse haben Sie bereits im zweiten Kapitel verwendet, und Sie kommt auch im Beispielprojekt Fototagebuch zum Einsatz.

Mit Objekten der Klasse NSCalendar berechnen Sie basierend auf einer Zeitzone und einer Region die Datumskomponenten zu einem Zeitpunkt und umgekehrt. Mit dieser Klasse und ein bisschen Rechnerei können Sie also die Positionen der Uhrzeiger zu einem Zeitpunkt bestimmen. Aus diesem Grund enthält die Klasse eine weitere, haltende Property namens calendar vom Typ NSCalendar. Mit der Methode components:fromDate: aus der Kalenderklasse können Sie ein NSDateComponents-Objekt berechnen lassen, das die benötigten Zeitkomponenten (Stunde, Minute und Sekunde) enthält. Mit den oben angegebenen Formeln können Sie daraus dann die entsprechenden Winkel für die Darstellung bestimmen.

Wenn Sie allerdings die Stunde in einen Winkel umrechnen, bekommen Sie einen Stundenzeiger, der immer nur auf volle Stunden zeigt. Bei einer analogen Uhr soll er jedoch beispielsweise um halb fünf genau zwischen der vierten und fünften Stunde stehen. Das können Sie erreichen, indem Sie zum Stundenwert den Minutenanteil hinzurechnen. Da eine Stunde bekanntlich 60 Minuten hat, ist der Minutenanteil einer Stunde ein Sechzigstel des Minutenwertes.

NSDateComponents *theComponents =
[self.calendar components:NSHourCalendarUnit |Zeilenumbruch
NSMinuteCalendarUnit |Zeilenumbruch
NSSecondCalendarUnitZeilenumbruch
fromDate:self.time];
CGFloat theSecond = theComponents.second * M_PI / 30.0;
CGFloat theMinute = theComponents.minute * M_PI / 30.0;
CGFloat theHour = (theComponents.hour + Zeilenumbruch
theComponents.minute / 60.0) * M_PI / 6.0;

Listing 3.9 Berechnung der Winkel für Sekunde, Minute und Stunde

Die Variablen theSecond, theMinute und theHour enthalten den Winkel für die entsprechende Zeigerstellung. Der Faktor p / 30 = 2p / 60 in der Berechnung entspricht dabei dem Winkel im Bogenmaß von einer Sekunde beziehungsweise einer Minute. Analog ist p / 6 = 2p / 12 der Winkel, der einer vollen Stunde auf einem Ziffernblatt entspricht.

Ist er Ihr Typ?

Bei der Berechnung des Minutenanteils verwendet Listing 3.9 nicht 60, sondern 60.0 als Teiler. Der Unterschied dieser beiden Werte ist ihr Datentyp. Während 60 den Typ int hat, ist 60.0 ein double. Da der Minutenwert theComponents.minute den Typ NSInteger hat, würde bei einer Division durch 60 eine Ganzzahldivision ausgeführt. Der Minutenwert liegt immer zwischen 0 und 59, bei einer Ganzzahldivision kommt dabei also immer – Tusch – 0 heraus. Die Rechnung können Sie sich also auch sparen. Bei einer Fließkommadivision erhalten Sie hingegen Werte zwischen 0 und 0,99.

Jetzt brauchen Sie nur noch die drei Zeiger zu zeichnen, indem Sie eine Linie vom Mittelpunkt des Views in Richtung des jeweiligen Winkels ziehen. Die drei Linien unterscheiden sich jedoch nicht nur hinsichtlich ihres Winkels, sondern auch in der Länge, der Farbe und der Linienstärke.

Den Zielpunkt der Zeiger berechnen Sie jeweils über die Methode pointWithRadius:angle:, wobei Sie die unterschiedlichen Längen durch die Verwendung unterschiedlicher Radien erzielen. Das Zeichnen der einzelnen Zeiger ist recht ähnlich und unterscheidet sich nur in den unterschiedlichen Zeichenattributen für die Linien. Die gesamte Methode drawClockHands sieht dann wie folgt aus:

- (void)drawClockHands {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGPoint theCenter = [self midPoint];
CGFloat theRadius = CGRectGetWidth(self.bounds) / 2.0;
NSDateComponents *theComponents = Zeilenumbruch
[self.calendar components:NSHourCalendarUnit Zeilenumbruch
NSMinuteCalendarUnit Zeilenumbruch
NSSecondCalendarUnit Zeilenumbruch
fromDate:self.time];
CGFloat theSecond = theComponents.second * M_PI / 30.0;
CGFloat theMinute = theComponents.minute * M_PI / 30.0;
CGFloat theHour = (theComponents.hour + Zeilenumbruch
theComponents.minute / 60.0) * M_PI / 6.0;
CGPoint thePoint = [self pointWithRadius:theRadius * 0.7 Zeilenumbruch
angle:theHour];

CGContextSetRGBStrokeColor(theContext,Zeilenumbruch
0.25, 0.25, 0.25, 1.0);
CGContextSetLineWidth(theContext, 7.0);
CGContextSetLineCap(theContext, kCGLineCapButt);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
thePoint = [self pointWithRadius:theRadius * 0.9 Zeilenumbruch
angle:theMinute];
CGContextSetLineWidth(theContext, 5.0);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
thePoint = [self pointWithRadius:theRadius * 0.95 Zeilenumbruch
angle:theSecond];
CGContextSetLineWidth(theContext, 3.0);
CGContextSetRGBStrokeColor(theContext, 1.0, 0.0, 0.0, 1.0);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
}

Listing 3.10 So werden die Uhrzeiger des Weckers gezeichnet.


Galileo Computing - Zum Seitenanfang

3.1.3 View-Erzeugung über NIB-DateienZur nächsten ÜberschriftZur vorigen Überschrift

Sie müssen den beiden Propertys calendar und time vor der ersten Anzeige des Views nun noch sinnvolle Werte zuweisen. Dazu können Sie die Initialisierungsmethoden entsprechend erweitern. Die betreffenden Werte müssen Sie in der Methode dealloc natürlich auch wieder freigeben, indem Sie die Propertys auf nil setzen.

Der Convenience-Konstruktor date der Klasse NSDate erzeugt ein Datumsobjekt mit der aktuellen Uhrzeit in GMT, und currentCalendar liefert ein Kalenderobjekt, das den aktuellen Einstellungen des iPhones entspricht.

- (id)initWithFrame:(CGRect)inFrame {
self = [super initWithFrame:inFrame];
if(self) {
self.calendar = [NSCalendar currentCalendar];
self.time = [NSDate date];
}
return self;
}
- (void)dealloc {
self.calendar = nil;
self.time = nil;
[super dealloc];
}

Listing 3.11 Initialisierung und Freigabe der Propertys der Zeitanzeige

Ihr Programm verwendet die Initialisierungsmethode initWithFrame: allerdings nur, wenn Sie Ihren View direkt über Programmcode erzeugen. Die App lädt diesen View jedoch aus einer NIB-Datei oder einem Storyboard und ruft dabei diese Methode nicht auf.

Für diesen Fall gibt es zwei mögliche Alternativen. Sie können eine weitere Initialisierungsmethode namens initWithCoder: oder eine Methode namens awakeFromNib implementieren. Sie dürfen dabei eine oder auch beide Methoden verwenden. Die Initialisierung eines Objekts aus einer NIB-Datei erfolgt über initWithCoder:, sofern sie diese Methode besitzt. Bei allen anderen Klassen kommt die Initialisierungsmethode init zum Einsatz. Da die Klasse UIView das Protokoll NSCoder adaptiert und somit diesen Initializer bereitstellt, erfolgt die Initialisierung immer darüber. Sind alle Objekte aus der NIB-Datei geladen und initialisiert, sendet Cocoa Touch an jedes Objekt die Nachricht awakeFromNib, die es von NSObject erbt. Diese Methode können Sie also überschreiben, um ein Objekt erst dann zu initialisieren, wenn Cocoa Touch alle Objekte aus der NIB-Datei geladen hat. Den Ablauf für die Initialisierung eines Views stellt Abbildung 3.15 grafisch dar.

Abbildung

Abbildung 3.15 Ablauf der Initialisierung für Views aus NIB-Dateien

Wie bereits weiter oben erwähnt wurde, sind alle Objekte in einer NIB-Datei als serialisierte Daten abgelegt. Die Serialisierung enthält alle notwendigen Informationen, um diese Objekte wiederherzustellen. Zu diesen Informationen gehören die Klassen, die Attributwerte und die Anordnung der Objekte zueinander. Während NIB-Dateien dafür ein kompaktes, binäres Dateiformat verwenden, verwenden XIB-Dateien und Storyboards ein XML-Format.

Wenn die Deserialisierung die Methode initWithCoder: des neuen Objekts aufruft, übergibt sie einen Parameter vom Typ NSCoder mit den notwendigen Daten für dieses Objekt. Da Ihre Klasse jedoch die Serialisierung nicht erweitert, brauchen Sie den Parameter nur für den Initialisierer-Aufruf der Oberklasse. Wenn Sie die Methode initWithCoder: implementieren, sollten Sie immer als Erstes diese Methode der Oberklasse so wie in Listing 3.12 aufrufen:

- (id)initWithCoder:(NSCoder *)inCoder {
self = [super initWithCoder:inCoder];
if(self) {
self.calendar = [NSCalendar currentCalendar];
self.time = [NSDate date];
}
return self;
}

Listing 3.12 Initialisierung der Propertys bei der Deserialisierung

Nach der Erzeugung aller Objekte aus einer NIB-Datei ruft Cocoa Touch die Methode awakeFromNib bei allen erzeugten Objekten auf. Wenn Sie diese Methode überschreiben, sollten Sie auch immer als Erstes die Methode in der Oberklasse aufrufen.

- (void)awakeFromNib {
[super awakeFromNib];
self.calendar = [NSCalendar currentCalendar];
self.time = [NSDate date];
}

Listing 3.13 Initialisierung der Propertys nach der Deserialisierung

Verlassen Sie sich weder bei initWithCoder: noch bei awakeFromNib auf eine feste Aufrufreihenfolge. Für die Initialisierung ist häufig awakeFromNib die geeignetere Wahl, da bei Aufruf dieser Methode auch alle anderen Objekte des NIB-Files die Initialisierung durchlaufen haben.


Galileo Computing - Zum Seitenanfang

3.1.4 Aktualisierung der ZeitanzeigeZur nächsten ÜberschriftZur vorigen Überschrift

Als Nächstes soll die Zeitanzeige kontinuierlich aktualisiert werden. Diese Aufgabe kann entweder der View selbst oder der Viewcontroller übernehmen. Da jedoch jedes Objekt der Klasse ClockView diese Aufgabe erfüllen sollte, wird der dafür nötige Code in der View-Klasse angesiedelt.

Für die Aktualisierung der Zeitanzeige müssen Sie nur in regelmäßigen Abständen den Wert der Property time aktualisieren. Wenn Sie eine kurze Aufgabe wiederholt ausführen möchten, können Sie dafür die Klasse NSTimer verwenden.

Die Klasse ClockView erhält dazu eine weitere Property von diesem Typ und jeweils eine Methode zum Starten und zum Stoppen der Aktualisierung.

Anonyme Kategorie

Die Klasse verwendet die Property timer nur intern. Eine Zugriffsmöglichkeit von außen ist also nicht notwendig. Das lässt sich durch eine anonyme Kategorie vermeiden. Bei einer anonymen Kategorie geben Sie bei der Deklaration zwischen den runden Klammern keinen Namen an, und als Implementierungsteil verwenden Sie den der Klasse.

In der Regel deklarieren Sie die anonyme Kategorie einer Klasse in deren Implementierungsdatei. Sie können die Timer-Property also in der anonymen Kategorie deklarieren, da sie für andere Klassen nicht sichtbar sein soll:

@interface ClockView()
@property(nonatomic, retain) NSTimer *timer;
@end
@implementation ClockView
@synthesize timer;
...
@end

Die neueren Xcode Versionen legen übrigens bei Viewcontroller-Klassen automatisch jeweils eine anonyme Kategorie an.

Sie können so die Property vor dem allgemeinen Zugriff verstecken. In der anonymen Kategorie können Sie sämtliche Methoden und Propertys deklarieren, die die Klasse ClockView nur intern braucht und die sie nicht für die Öffentlichkeit zur Verfügung stellt. Die Verwaltung des Property-Inhalts erfolgt über die beiden Methoden startAnimation und stopAnimation, auf die auch andere Klassen zugreifen dürfen.

@interface ClockView : UIView
...
- (void)startAnimation;
– (void)stopAnimation;
@end

Listing 3.14 Deklarationen für die automatische Aktualisierung der Zeitanzeige

Ein Timer kann eine Methode in einem Objekt nach einer festen Zeitspanne aufrufen. Er kann das einmal oder auch kontinuierlich machen. In der Methode startAnimation müssen Sie also ein Timer-Objekt erzeugen und starten, wogegen Sie es in stopAnimation anhalten und zerstören. Sie können über die Property für den Timer außerdem verhindern, dass die App den Timer versehentlich mehrmals startet. Dazu prüft die Methode in Listing 3.15 über die Bedingung self.timer == nil, ob die Property timer nicht schon etwa auf einen Timer verweist:

- (void)startAnimation {
if(self.timer == nil) {
self.timer = [NSTimerZeilenumbruch
scheduledTimerWithTimeInterval:0.5Zeilenumbruch
target:self selector:@selector(updateTime:)Zeilenumbruch
userInfo:nil repeats:YES];
}
}
- (void)stopAnimation {
[self.timer invalidate];
self.timer = nil;
}

Listing 3.15 Starten und Stoppen der Animation

Der Aufruf von scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: in der dritten Zeile erzeugt und startet einen kontinuierlichen Timer und weist ihn der Property timer zu. Dabei geben der zweite und der dritte Parameter an, dass der Timer die Methode updateTime: im aktuellen Objekt aufrufen soll. Der Doppelpunkt im Selektor zeigt an, dass diese Methode einen Parameter erwartet. Das Zeitintervall beträgt eine halbe Sekunde, damit der Timer die Anzeige zweimal pro Sekunde aktualisiert. Da die Methodenaufrufe nicht in exakten Zeitabständen erfolgen, könnten bei längeren Zeitintervallen (z. B. einer Sekunde) sonst Sprünge auftreten, was zu ruckelnden Zeigerbewegungen führt.

Anonyme Kategorien erlauben auch das Überschreiben der Read-Only-Eigenschaft von Propertys. Dazu müssen Sie die Property, die Sie als readonly in der Schnittstelle der Klasse deklariert haben, genau mit den gleichen Parametern und dem Parameter readwrite in der anonymen Kategorie deklarieren. Sie dürfen keinen Parameter – bis auf readonly in readwrite – bei der Redeklaration verändern. Außerdem müssen Sie natürlich im Header auch den gleichen Speicherverwaltungstyp angeben. Die Property calendar der Klasse ClockView soll von außen unveränderbar sein:

// Deklaration in ClockView.h
@property (nonatomic, retain, readonly) Zeilenumbruch
NSCalendar *calendar;
// Deklaration in der anonymen Kategorie in ClockView.m
@property (nonatomic, retain, readwrite) Zeilenumbruch
NSCalendar *calendar;

Listing 3.16 Öffentliche Read-Only- und private Read-Write-Property

Sie können nach dieser Änderung in der Implementierung von ClockView die Property calendar wie bisher verwenden. Außerhalb der Implementierung können Sie die Property dagegen jetzt nur noch auslesen. Eine Veränderung des Wertes ist nicht mehr möglich.

Tipp

Verwenden Sie anonyme Kategorien, um Implementierungsdetails Ihrer Klassen nach außen hin zu verstecken. Anonyme Kategorien erlauben Ihnen eine klarere Strukturierung Ihrer Schnittstellen.

Die Methode updateTime: aktualisiert den Wert der Property time. Damit Cocoa Touch den View danach auch neu zeichnet, müssen Sie auch noch die Methode setNeedsDisplay aufrufen. Sie zeichnet den View allerdings nicht direkt neu, sondern markiert ihn nur zum Neuzeichnen. Das macht das Betriebssystem erst dann, wenn es dafür Zeit hat.

- (void)updateTime:(NSTimer *)inTimer {
self.time = [NSDate date];
[self setNeedsDisplay];
}

Listing 3.17 Aktualisierung der Zeitanzeige

Automatische Aktualisierung des Views versus Animationen

Die in diesem Abschnitt vorgestellten Methoden dienen zur automatischen Aktualisierung eines Views. Dieses Vorgehen ist für diesen Anwendungsfall auch durchaus ausreichend, da Sie die Zeiger nur zweimal pro Sekunde neu zeichnen müssen. Bei jeder Aktualisierung zeichnet der View nicht nur die Zeiger, sondern auch das komplette Ziffernblatt neu. Die Uhr läuft trotzdem flüssig.

Dieses Vorgehen eignet sich allerdings nicht für beliebige Animationen, da dort in der Regel häufigere Aktualisierungen des Views notwendig sind. Außerdem gibt Ihnen Cocoa Touch dafür mit Core Animation ein viel besseres System an die Hand, und einfache Animationen lassen sich außerdem problemlos über Klassenmethoden von UIView realisieren. Mehr dazu finden Sie in Kapitel 6, »Models, Layer, Animationen«.


Galileo Computing - Zum Seitenanfang

3.1.5 Wiederverwendbarkeit von ViewsZur vorigen Überschrift

Die Klasse ClockView weist bis jetzt keine Abhängigkeiten vom Viewcontroller oder vom Application-Delegate auf. Sie ist also vollkommen unabhängig von der Controller- oder sogar der Modellschicht des Programms. Das ist so gewollt und soll auch so bleiben. Die Unabhängigkeit erlaubt es Ihnen, beliebig viele Uhren in Ihrem Projekt anzulegen.

Sie können also erneut die XIB-Datei öffnen und weitere ClockView-Objekte so, wie oben beschrieben, anlegen. Sie brauchen dazu einfach nur im Interface Builder weitere UIView-Objekte auf den Hauptview ziehen und die Klasse der neuen Objekte auf ClockView zu setzen. Sie können sogar die Header- und die Implementierungsdatei der Klasse in ein anderes Projekt übertragen und den View dort ohne Einschränkungen oder weitere notwendige Schritte verwenden. Die Größen der Uhren lassen sich über den Größeninspektor auch beliebig anpassen. Sie können sogar Ziffernblätter erzeugen, die nicht kreis-, sondern ellipsenförmig sind. Letztere sehen allerdings etwas gewöhnungsbedürftig aus. Außerdem können Sie die Farbe für den Hintergrund hinter dem Ziffernblatt frei wählen. Ein Beispiel dafür sehen Sie in Abbildung 3.16.

Abbildung

Abbildung 3.16 Wiederverwendung eines Views

Ein möglichst hoher Wiederverwendungsgrad sollte immer Ihr Ziel bei der Entwicklung eigener View-Komponenten sein. Zugegebenermaßen kann der View auch nur eine Uhrzeit darstellen. Sie lernen im Verlauf dieses Buches noch weitere Möglichkeiten kennen, wie der View mit den Objekten aus den anderen Schichten interagieren kann, ohne dass dabei Abhängigkeiten zu Klassen aus diesen Schichten entstehen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück
  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Jetzt 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: Apps entwickeln für iPhone und iPad - Videotraining






 Apps entwickeln für
 iPhone und iPad -
 Videotraining


Zum Katalog: Apps mit HTML5 und CSS3






 Apps mit HTML5
 und CSS3


Zum Katalog: iPhone- und iPad-Apps entwickeln






 iPhone- und
 iPad-Apps entwickeln


Zum Katalog: Android 4






 Android 4


Zum Katalog: Android-Apps entwickeln - Videotraining






 Android-Apps
 entwickeln -
 Videotraining


Zum Katalog: Windows Store Apps mit XAML und C#






 Windows Store Apps
 mit XAML und C#


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2013
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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de