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

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

Buch bestellen
Ihre Meinung?

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

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 3 Sehen und anfassen
Pfeil 3.1 Eigene Viewklassen in Cocoa Touch
Pfeil 3.1.1 Zeichnen in Cocoa Touch
Pfeil 3.1.2 Zeitberechnung
Pfeil 3.1.3 View-Erzeugung über Storyboards
Pfeil 3.1.4 Aktualisierung der Zeitanzeige
Pfeil 3.1.5 Wiederverwendbarkeit von Views
Pfeil 3.1.6 Zeichenfarbe festlegen
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 View-Hierarchien
Pfeil 3.2.5 Actions
Pfeil 3.2.6 Ereignisse
Pfeil 3.2.7 Controlzustände und Buttons
Pfeil 3.2.8 Touch-A, Touch-A, Touch Me
Pfeil 3.2.9 Übergänge
Pfeil 3.2.10 Ein kleines Intermezzo über Labels
Pfeil 3.2.11 Beliebige Objekte im Storyboard
Pfeil 3.2.12 Der Lebenszyklus eines Viewcontrollers
Pfeil 3.2.13 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 Launch-Images
Pfeil 3.4.3 Sprachkursus für die App
Pfeil 3.4.4 Es funktioniert nicht
Pfeil 3.4.5 Unterstützung älterer iOS-Versionen
Pfeil 3.4.6 Base-Internationalisierung ausschalten
Pfeil 3.4.7 Universelle Apps
Pfeil 3.5 Geräteausrichtungen, Autosizing und Autolayout
Pfeil 3.5.1 Flexible Views dank Autosizing
Pfeil 3.5.2 Autolayout
Pfeil 3.5.3 Restriktionen im Interface-Builder festlegen
Pfeil 3.5.4 Autolayout und Lokalisierung
Pfeil 3.6 Fehlersuche
Pfeil 3.6.1 Logging
Pfeil 3.6.2 Der Debugger
Pfeil 3.6.3 Breakpoints verwalten
Pfeil 3.6.4 Die Debugger-Konsole

Rheinwerk Computing - Zum Seitenanfang

3.2Views und ViewcontrollerZur nächsten Überschrift

Das Beispielprojekt Clocks zeigt zwar mehrere Uhren an, deren Zeiger bleiben aber stehen. Das liegt daran, dass dieses Programm nicht die Methode startAnimation aufruft. Wenn Sie diesen Aufruf in die Methode awakeFromNib in der Klasse ClockView einfügen, bewegen sich die Zeiger. Allerdings startet dann das Laden des Views die Timer, und erst der Aufruf der Methode dealloc der Uhren stoppt sie. Die Uhren laufen also ständig. Bei so einer kleinen App, die nur eine Ansicht zeigt, ist das zwar nicht schlimm. In größeren Projekten ist es sicherlich sinnvoller, die Timer nur dann laufen zu lassen, wenn die App sie auch tatsächlich anzeigt. Die Klasse UIView bietet dafür jedoch keine entsprechenden Methoden an, und dafür ist sie auch nicht da.

Für die generellen Abläufe in der Applikation ist schließlich die Controller-Schicht zuständig. Und deshalb sollte der Viewcontroller diese Aufgabe übernehmen. In Cocoa Touch leiten sich die Viewcontroller von der Klasse UIViewController ab. Sie bietet vier Methoden an, die sie bei einer Änderung des Anzeigezustandes aufruft und die Sie in Ihren Unterklassen überschreiben können. Die Methoden heißen viewWillAppear:, viewDidAppear:, viewWillDisappear: und viewDidDisappear: und haben jeweils einen booleschen Parameter.

Während Cocoa Touch die ersten beiden Methoden bei der Anzeige des Views aufruft, ruft es die letzten beiden beim Verschwinden des Views auf. Der Namensbestandteil Will bedeutet, dass Cocoa Touch die Methode vor dem Ereignis aufruft, während Did darauf hinweist, dass Cocoa Touch die Methode nach dem Ereignis aufruft. Der boolesche Parameter gibt dabei jeweils an, ob die Anzeige oder das Verschwinden animiert erfolgt. Wenn Sie diese Methoden überschreiben, müssen Sie auch immer die entsprechende Methode in der Oberklasse aufrufen.

Sie könnten jetzt also die Uhren in viewDidAppear: starten und in viewWillDisappear: stoppen und stellen damit sicher, dass die Uhr immer läuft, wenn sie die Applikation anzeigt. Sie müssen dazu nur noch Ihren Viewcontroller mit den Uhren bekannt machen.

Sie können in Storyboards nicht nur den Viewaufbau speichern, sondern auch Verbindungen zwischen den Objekten herstellen. Das ist in der Regel der Viewcontroller. Es gibt vier Verbindungstypen:

  • Actions sind Verbindungen zwischen Views, die Ereignisse versenden, und Methoden, die diese Ereignisse verarbeiten. Damit können Sie beispielsweise einen Methodenaufruf auslösen, wenn Sie einen Button drücken. Dieser Verbindungstyp ist Gegenstand von Abschnitt 3.2.5, »Actions«.
  • Ein Outlet verbindet ein Objekt im Storyboard mit einem Attribut oder einer Property eines anderen Objekts. Im ersten Kapitel haben Sie zum Beispiel ein Label über ein Outlet mit dem Viewcontroller verbunden.
  • Eine Outlet-Collection verbindet mehrere Objekte mit einer Array-Property. Sie können über Outlet-Collections also auf mehrere gleichartige Objekte des Storyboards zugreifen.
  • Eine Segue oder auch Übergang verbindet in einem Storyboard jeweils einen Viewcontroller oder einen View, der Ereignisse versendet, mit einem Viewcontroller. Der Übergang beschreibt dabei den Wechsel von einem Viewcontroller zu einem anderen. Übergänge behandelt Abschnitt 3.2.9.

Rheinwerk Computing - Zum Seitenanfang

3.2.1OutletsZur nächsten ÜberschriftZur vorigen Überschrift

Outlets erzeugen Sie über den Interface-Builder. Öffnen Sie dazu den View des Beispielprojekts im Storyboard des Beispielprojekts. Aktivieren Sie in der Werkzeugleiste den Hilfseditor durch den mittleren Button. Der Hilfseditor sollte nun die Headerdatei AlarmClockViewController.m des Viewcontrollers anzeigen. Sie können die angezeigte Datei über die Sprungleiste des Editors unterhalb der Werkzeugleiste ändern, falls der Editor eine andere Datei geöffnet haben sollte.

Wenn Sie jetzt mit der rechten Maustaste (oder mit der linken bei gedrückter ctrl-Taste) auf das Ziffernblatt klicken, können Sie eine Verbindung von dem View zu der Headerdatei des Controllers ziehen, wie Abbildung 3.19 zeigt.

Abbildung

Abbildung 3.19 Ziehen einer Outlet-Verbindung

Header- oder Implementierungsdatei

Sie können Verbindungen sowohl in die anonyme Kategorie als auch in die Klassendeklaration ziehen; das hängt nur von Ihrer Entscheidung ab. Wenn Sie die Verbindung in die anonyme Kategorie ziehen, können allerdings andere Klassen nicht darauf zugreifen. Das kann sowohl ein Vor- wie auch ein Nachteil sein.

Nachdem Sie die Maustaste innerhalb der Klassendeklaration losgelassen haben, erscheint der in Abbildung 3.20 abgebildete Pop-over-Dialog, über den Sie ein Outlet oder eine Outlet-Collection anlegen können. Geben Sie »clockView« in das Textfeld ein, und lassen Sie die übrigen Einstellungen des Dialogs unverändert.

Abbildung

Abbildung 3.20 Pop-over-Dialog für das Outlet

Durch einen Klick auf den Button Connect erstellen Sie im Quelltext ein Attribut und eine Property, vor deren Klasse die Markierung IBOutlet steht. Diese Markierung ist ein leeres C-Makro, das keine Auswirkung auf den erzeugten Programmcode hat. Es dient lediglich dazu, dem Interface-Builder anzuzeigen, dass er zu dieser Property Verbindungen herstellen kann. Allerdings ist die Klasse ClockView in der Datei AlarmClockViewController.m noch unbekannt, und Sie müssen die Headerdatei ClockView.h importieren, um Übersetzungsfehler zu vermeiden. Die ersten Programmzeilen nach dem Kopfkommentar in der Datei AlarmClockViewController.m sehen damit folgendermaßen aus:

#import "AlarmClockViewController.h"
#import "ClockView.h"

@interface AlarmClockViewController ()

@property (weak, nonatomic) IBOutlet ClockView *clockView;

@end

Listing 3.23 Outlet-Property für den Clockview

Vorwärts, Deklarationen!

Falls Sie das Outlet lieber in der Headerdatei deklarieren wollen, müssen Sie nicht unbedingt die Headerdatei ClockView.h importieren; Sie können stattdessen auch eine Vorwärtsdeklaration für die Klasse mit der Anweisung @class ClockView; verwenden. Eine Vorwärtsdeklaration sagt dem Compiler nur, dass es die Klasse gibt, jedoch nicht, wie sie aussieht oder was sie kann. Die Header des Viewcontrollers sieht damit so aus:

#import <UIKit/UIKit.h>

@class ClockView;

@interface AlarmClockViewController : UIViewController

@property (weak, nonatomic) IBOutlet ClockView *clockView;

@end

Vorwärtsdeklarationen sind insbesondere bei der Vermeidung zyklischer Imports (z. B. A.h importiert B.h und umgekehrt) nützlich.

Xcode zeigt in der Zeile mit einem Outlet vor der Zeilennummer einen leeren oder ausgefüllten Kreis an. Ein leerer Kreis bedeutet, dass keine Verbindung zu dem Outlet besteht, und ein ausgefüllter, dass das Outlet verbunden ist. Wenn Sie den ausgefüllten Kreis anklicken, zeigt Xcode den verbundenen View im Interface-Builder an.

Der Speicherverwaltungstyp der Property hat den Typ weak. Sie können über das Feld Storage auch den Typ strong wählen. Xcode verwendet für Top-Level-Elemente automatisch den Typ strong und für alle darunterliegenden Elemente den Typ weak. Dabei sind Top-Level-Elemente alle Elemente, die direkt unterhalb der Szene liegen. Das ist normalerweise nur der Viewcontroller; Sie können jedoch auch weitere Objekte als Top-Level-Elemente anlegen, wie Abbildung 3.21 zeigt.

Abbildung

Abbildung 3.21 Einfügen eines Top-Level-Elements

Bei Top-Level-Elementen sollten Sie immer den Speicherverwaltungstyp strong verwenden, da kein anderes Objekt diese Objekte hält. Bei dem Speicherverwaltungstyp weak gäbe Cocoa Touch das Objekt unmittelbar nach dem Laden frei, und die Property hätte den Wert nil. Für alle Elemente unterhalb der Top-Level-Elemente ist hingegen weak geeigneter. Wenn beispielsweise der Viewcontroller die View-Hierarchie freigibt, müssen Sie den Property-Wert nicht explizit auf nil setzen.

Outlets in älteren Projekten

In älteren Projekten finden Sie in den Viewcontoller-Klassen häufig noch die inzwischen veraltete Methode viewDidUnload, die die Outlet-Propertys auf nil setzt. Das ist seit iOS 6 nicht mehr notwendig, weil der Viewcontroller seinen View nicht mehr wegen Speicherwarnungen freigibt. Er gibt den View erst bei seiner eigenen Zerstörung frei.

Abbildung

Abbildung 3.22 Der Verbindungsinspektor zeigt Verbindungen an.

Sie können sich die gesetzten und möglichen Verbindungen eines Objekts im Interface-Builder über den Verbindungsinspektor ansehen (siehe Abbildung 3.22). Dabei listet die Rubrik Outlets alle ausgehenden Verbindungen auf, unabhängig davon, ob sie gesetzt sind oder nicht. Referencing Outlets enthält alle eingehenden Verbindungen; hier finden Sie natürlich nur tatsächlich existierende Verbindungen. Wenn Sie die Maus über eine Verbindung im Inspektor bewegen, hebt der Interface Builder den damit verbundenen View in der Ansicht hervor.

Indem Sie die entsprechenden Anweisungen in die Methoden viewDidAppear: und viewWillDisappear: einfügen, können Sie über den Viewcontroller jetzt eine Uhr starten. Die kompletten Methoden müssen Sie in die Implementierungsdatei AlarmClockViewController.m schreiben:

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
[self.clockView startAnimation];
}
- (void)viewWillDisappear:(BOOL)inAnimated {
[self.clockView stopAnimation];
[super viewWillDisappear:inAnimated];
}

Listing 3.24 Starten und Stoppen der Animation über den Viewcontroller

Wenn Sie jetzt Ihr Programm im Simulator ausführen, bewegen sich die Zeiger einer Uhr.


Rheinwerk Computing - Zum Seitenanfang

3.2.2Outlet-CollectionsZur nächsten ÜberschriftZur vorigen Überschrift

Das Beispielprojekt Clocks zeigt mehrere Clockviews an. Um diese Uhren zu starten und zu stoppen, können Sie für jede Uhr auch ein eigenes Outlet mit den entsprechenden Start- und Stoppanweisungen anlegen. Das ist jedoch sehr unelegant und sieht nicht sehr schön aus, da Sie mehrmals die gleichen Anweisungen schreiben müssen. Außerdem müssen Sie sich für jedes Outlet einen neuen Namen überlegen. Für das mehrfache Ausführen der gleichen Anweisung gibt es doch schließlich Schleifen.

Über Outlet-Collections können Sie mehrere Objekte auf einmal zu verbinden. Sie erzeugen sie ähnlich wie Outlets, indem Sie eine Verbindung von einem Objekt im Interface Builder in die Implementierungsdatei des Controllers ziehen. Lassen Sie die Maustaste los, und wählen Sie im Pop-over-Dialog unter Connection den Punkt Outlet Collection aus. Als Namen verwenden Sie »clockViews« (siehe Abbildung 3.23).

Abbildung

Abbildung 3.23 Anlegen einer Outlet-Collection

Nach einem Klick auf den Connect-Button legt Xcode eine Property mit dem Typ NSArray an und fügt eine Markierung IBOutletCollection(ClockView) vor der Deklaration des Typs ein. Die Deklaration in der anonymen Kategorie sieht so aus:

@property (strong, nonatomic) IBOutletCollection(ClockView) 
NSArray *clockViews;

Propertys für Outlet-Collections müssen immer den Speicherverwaltungstyp strong haben, auch wenn die verbundenen Elemente keine Top-Level-Elemente sind, da es keinen weiteren Halter für das Array gibt. Das Array gehört ja schließlich nicht zur View-Hierarchie dazu.

Auch hier müssen Sie gegebenenfalls die Headerdatei ClockView.h importieren, falls das nicht bereits geschehen ist. Der Makroparameter ClockView in der Deklaration schränkt im Interface Builder die möglichen Objekte für die Zuweisung auf die Klasse ClockView ein. Sie können jetzt der Outlet-Collection beliebig viele Clockviews zuweisen; leider muss das jedoch für jeden View einzeln erfolgen. Dabei können Sie wie bisher ein Gummiband mit einem Rechtsklick vom View auf die Property-Deklaration in der anonymen Kategorie ziehen, oder Sie öffnen den Verbindungsinspektor des Viewcontrollers und ziehen eine Verbindung mit einem Linksklick von der Outlet-Collection zum View (siehe Abbildung 3.24). Sie können einzelne Verbindungen übrigens durch einen Klick auf das Kreuzchen vor dem Namen wieder lösen.

Abbildung

Abbildung 3.24 Outlet-Collection erweitern

Löschen von Verbindungen

Zum Löschen einer Verbindung – egal ob Outlet, Outlet-Collection oder Action – reicht es nicht aus, die Property beziehungsweise die Methode zu löschen. Sie müssen auch immer die Verbindung im Storyboard über den Interface Builder löschen.

Durch diesen Verbindungstyp können Sie jetzt zum Starten und Stoppen der Uhren eine Schleife verwenden, und nach dem Ausführen der App im Simulator laufen tatsächlich alle Uhren, die Sie über die Outlet-Collection verbunden haben.

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
for(ClockView *theView in self.clockViews) {
[theView startAnimation];
}
}

- (void)viewWillDisappear:(BOOL)inAnimated {
for(ClockView *theView in self.clockViews) {
[theView stopAnimation];
}
[super viewWillDisappear:inAnimated];
}

Listing 3.25 Starten und Stoppen der Uhren über eine Outlet-Collection

Achtung

Outlet-Collections verwenden zwar Arrays. Sie können jedoch die Reihenfolge der Elemente in dem Array nicht durch den Interface Builder festlegen.


Rheinwerk Computing - Zum Seitenanfang

3.2.3ContainerviewsZur nächsten ÜberschriftZur vorigen Überschrift

Wenn die Reihenfolge der Views wichtig ist, können Sie statt der Outlet-Collection auch einen View als Container verwenden. Sie legen dazu alle Views, auf die Sie gesammelt zugreifen möchten, in einem gemeinsamen, gegebenenfalls nicht sichtbaren View an, und auf diesen View greifen Sie über ein herkömmliches Outlet zu. Häufig verwendet man für diese Containerviews ein UIView mit transparenter Hintergrundfarbe, der nur zur Beherbergung anderer Views dient. An die einzelnen Views gelangen Sie dann über die Property subviews des Container Views. Allerdings kann dabei natürlich jeder View nur in maximal einem Container View liegen.

In unserem Beispielprojekt existiert bereits ein View, den Sie als Containerview verwenden können. Es ist der Hauptview, in den Sie alle Zifferblätter gelegt haben. Für diesen Containerview brauchen Sie auch kein gesondertes Outlet, da Sie über die Property view des Viewcontrollers bereits auf diesen View zugreifen können. Sie können also die Outlet-Collection im letzten Listing durch die Property subviews der view-Property des Viewcontrollers ersetzen. Sie müssen dazu nur die Zeilen mit den Schleifenköpfen durch

for(ClockView *theView in self.view.subviews) {

ersetzen. Das funktioniert allerdings nur, solange Sie keine Views einer anderen Klasse in den Hauptview legen.


Rheinwerk Computing - Zum Seitenanfang

3.2.4View-HierarchienZur nächsten ÜberschriftZur vorigen Überschrift

Die Reihenfolge der Subviews im darüberliegenden View ist entscheidend für die Anzeige. In der Baumdarstellung sehen Sie, wie die Views verschachtelt sind und in welcher Reihenfolge sie innerhalb einer Ebene liegen. Diese Anordnung wirkt sich auf die Darstellung und die Verarbeitung der Eingaben (Berühren des Bildschirms oder auch Touches) aus. Je näher ein View zum Hauptview liegt, umso früher stellt Cocoa Touch ihn dar. Wenn also zwei Views einen gleichen Bereich auf dem Display verwenden, dann verdeckt der später dargestellte View den früher dargestellten.

In Abbildung 3.25 ist die Baumdarstellung eines Views zu sehen. Dabei haben die Views (1 bis 8) jeweils die gleichen Ziffern wie die Views in der Voransicht. Der rote View 2 ist am nächsten zum Hauptview, weswegen ihn alle anderen Views verdecken. Der gelbe View 6 liegt vor dem grünen 3 und verdeckt diesen somit, und der schwarze View 7 liegt im gelben View, wodurch er diesen verdeckt.

Abbildung

Abbildung 3.25 Baumdarstellung eines Views

Änderung der View-Hierarchie

Sie können auch in der Baumdarstellung die Anordnung der Views verändern. Ziehen Sie dazu die Views einfach an die gewünschte Position. Wenn Sie dort einen View auf einen anderen ziehen, fügt der Interface Builder den gezogenen View als letzten Subview des anderen Views ein.


Rheinwerk Computing - Zum Seitenanfang

3.2.5ActionsZur nächsten ÜberschriftZur vorigen Überschrift

Bislang haben die verwendeten Views nur etwas auf dem Bildschirm dargestellt. Viele Views können indes auch Eingaben verarbeiten. Im Gegensatz zu den meisten Bildschirmen von Desktop-Computern ist bei iOS-Geräten das Ausgabegerät ja auch gleichzeitig das Eingabegerät. Diese Views haben in der Regel die Klasse UIControl als Oberklasse und werden im Folgenden auch kurz als Controls bezeichnet. Typische Controls sind beispielsweise Buttons, Schieberegler und Textfelder.

Neben den Outlets können Sie auch Action-Verbindungen herstellen. Mit diesen Verbindungen verknüpfen Sie die von den Controls gesendeten Ereignisse mit einer Methode eines Controllers. Wenn Sie eine Action-Verbindung zu einem Control erzeugen, bekommt das Control ein Zielobjekt und eine Action-Methode übergeben. Dieses Vorgehen bezeichnet man auch als Target-Action-Mechanismus.

Action-Methoden müssen eine von drei vorgegebenen Signaturen haben und zumindest in der Deklaration für den Rückgabetyp das Makro IBAction verwenden. Dieses Makro wird während der Übersetzung durch den C-Typ void ersetzt. Es zeigt dem Interface Builder an, dass es sich bei einer Methode um eine Action-Methode handelt, zu der Action-Verbindungen erlaubt sind.

Die drei möglichen Signaturen für Action-Methoden sind:

- (IBAction)methodenName;
- (IBAction)methodenName:(id)inSender;
- (IBAction)methodenName:(id)inSender
forEvent:(UIEvent *)inEvent;

Listing 3.26 Mögliche Signaturen für Action-Methoden

Den Methodennamen können Sie dabei natürlich frei wählen. Der erste Parameter in der zweiten und dritten Variante enthält das Control, das das Ereignis abgeschickt hat. Als Typ wurde hier id verwendet, der auf jede Klasse passt. Sie dürfen jedoch auch einen konkreten Typ, beispielsweise UIButton *, verwenden. Dann können Sie in der Methode auch die Punktnotation für dieses Control verwenden.

Über den Event-Parameter der dritten Variante können Sie zusätzliche Informationen über das auslösende Ereignis ermitteln. Darüber erhalten Sie beispielsweise die genauen Fingerpositionen zum Auslösezeitpunkt. Die liefert Ihnen der Event in Objekten der Klasse UITouch. Eine Möglichkeit, die Fingerposition zu bestimmen, zeigt Listing 3.27. Dabei bestimmt die Methode locationInView: die Fingerposition immer relativ zu dem angegebenen View. Das Listing berechnet sie also relativ zu dem Control, das das Ereignis ausgelöst hat.

- (IBAction)touchWithSender:(id)inSender forEvent:(UIEvent *)inEvent {
UITouch *theTouch = [inEvent.allTouches anyObject];
CGPoint thePoint = [theTouch locationInView:inSender];
// Touchposition in thePoint auswerten
}

Listing 3.27 Ermittlung der Touchposition

Sie sollen jetzt einen Schalter (das ist ein View der Klasse UISwitch) dafür verwenden, den Animationsstatus der Uhren aus dem Clocks-Projekt zu steuern. Fügen Sie dazu einen UISwitch hinzu. Das ist ein Schiebeschalter mit zwei Zuständen für »an« und »aus«. Nach dem Einfügen ziehen Sie von dem Schiebeschalter ein Band in die anonyme Kategorie des Viewcontrollers. In dem Pop-over-Dialog haben Sie unter Connections nun eine zusätzliche Auswahlmöglichkeit Action, die Sie auswählen. Füllen Sie diesen Dialog wie in Abbildung 3.26 gezeigt aus.

Abbildung

Abbildung 3.26 Anlegen einer Action-Methode

Das Control sendet das Ereignis Value Changed immer dann, wenn sich der Wert ändert, den das Control verwaltet. Bei einem UISwitch ist das ein boolescher Wert, der die Schalterstellung repräsentiert.

Beim Anlegen der Verbindung hat Xcode nicht nur die Deklaration für die Action-Methode, sondern auch deren Implementierung erzeugt. Sie können nun zum Starten und Stoppen der Uhren die Schleifen aus Listing 3.25 kopieren. Allerdings ist das nicht besonders elegant. Es ist besser, diesen Code in eigene Methoden auszulagern, so dass Sie ihn von verschiedenen Stellen aus aufrufen können. Dazu können Sie das Kontextmenü RefactorExtract... verwenden. Selektieren Sie dazu eine der beiden Schleifen, und rufen Sie den Eintrag im Kontextmenü auf (siehe Abbildung 3.27).

Danach erscheint ein Dialog, in den Sie den Namen der neuen Methode eingeben. Verwenden Sie hier den Namen startAnimations, wie in Abbildung 3.28 gezeigt.

Danach erscheint der in Abbildung 3.29 gezeigte Vorschaudialog. Er zeigt Ihnen auf der rechten Seite den bestehenden und auf der linken Seite den neuen Code an. Wenn Sie auf Save klicken, ändert Xcode die Datei entsprechend ab.

Abbildung

Abbildung 3.27 Auslagern einer Schleife in eine Methode

Abbildung

Abbildung 3.28 Namen der neuen Methode festlegen

Abbildung

Abbildung 3.29 Vorschau für das Auslagern des Codes in eine Methode

Wenn Sie die zweite Schleife analog in die Methode stopAnimations auslagern, sieht der Programmcode danach wie folgt aus:

- (void)startAnimations {
for(ClockView *theView in self.clockViews) {
[theView startAnimation];
}
}

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
[self startAnimations];
}

- (void)stopAnimations {
for(ClockView *theView in self.clockViews) {
[theView stopAnimation];
}
}

- (void)viewWillDisappear:(BOOL)inAnimated {
[self stopAnimations];
[super viewWillDisappear:inAnimated];
}

Listing 3.28 Starten und Stoppen der Uhren mit eigenen Methoden

Mit Hilfe der beiden neuen Methoden können Sie nun die Action-Methode switchAnimation: folgendermaßen implementieren:

- (IBAction)switchAnimation:(UISwitch *)inSender {
if(inSender.on) {
[self startAnimations];
}
else {
[self stopAnimations];
}
}

Listing 3.29 Action-Methode zum Starten und Stoppen der Animation

Wenn Sie jetzt das Projekt ausführen, können Sie die Uhren über den Schalter starten und stoppen. Das Auslagern des Codes zum Starten und Stoppen der Uhren in jeweils eine eigene Methode hat den Vorteil, dass Sie diesen Code nur an einer Stelle anpassen müssen, wenn Sie ihn abändern wollen oder müssen. Beispielsweise können Sie die Schleifen durch die Methode makeObjectsPerformSelector: der Klasse NSArray ersetzen, die die angegebene Nachricht an alle Objekte in dem Array sendet.

- (void)startAnimations {
[self.clockViews makeObjectsPerformSelector:
@selector(startAnimation)];
}

- (void)stopAnimations {
[self.clockViews makeObjectsPerformSelector:
@selector(stopAnimation)];
}

Listing 3.30 Schleifen durch »makeObjectsPerformSelector:« ersetzen

Sie können den Zustand des Schalters über dessen Attributinspektor im Interface Builder festlegen. Hier wäre es natürlich gut, wenn der Zustand der Clockviews bei der ersten Anzeige davon abhinge. Dafür können Sie auch für den Schalter ein Outlet anlegen, in der Methode viewDidAppear: den Wert des Schalters darüber auslesen und in Abhängigkeit davon die Uhr starten. Neben dem Vorteil, den Anfangszustand der Uhren über den Interface Builder festzulegen, hat dieses Vorgehen den Vorteil, dass die Uhren ihren Animationszustand beibehalten, wenn die App zwischenzeitlich einen anderen Viewcontroller anzeigt. Das ist bei diesem kleinen Projekt mit einem Viewcontroller vielleicht kein so großer Vorteil, kann allerdings bei sehr umfangreichen und komplexen Applikationen sehr angenehm sein. Praktischerweise können Sie dafür sogar die Action-Methode verwenden. Wenn Sie die entsprechende Outlet-Property animationSwitch nennen, sieht der Code dafür so aus:

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
[self switchAnimation:self.animationSwitch];
}

Listing 3.31 Starten der Animation in Abhängigkeit von einem Schalter

Probieren Sie die neue Änderung aus, indem Sie im Attributinspektor des Schalters dessen Zustand unter State auf Off stellen. Nach dem Starten der App sollten nun alle Uhren stillstehen, und erst wenn Sie den Schalter umlegen, beginnen sie zu laufen.


Rheinwerk Computing - Zum Seitenanfang

3.2.6EreignisseZur nächsten ÜberschriftZur vorigen Überschrift

Sie haben im Beispielprogramm das Ereignis Value Changed (Wertänderung) eines Schalters in einer Methode Ihres Viewcontrollers verarbeitet. Ein Control kann noch eine Reihe weiterer Ereignisse senden. Diese Ereignisse lassen sich in drei Kategorien unterteilen:

  • Ereignisse für Wertänderungen
  • Ereignisse für Gesten
  • Ereignisse für Textänderungen

Ereignisse für Wertänderungen

Dieses Ereignis haben Sie bereits kennengelernt; es hat den Typ UIControlEventValueChanged. Es wird von Controls gesendet, die einen Wert manipulieren. Die Klassen, die dieses Ereignis versenden, finden Sie in Tabelle 3.1; die Spalte »Wert« enthält dabei den Namen der Methode, über die Sie den geänderten Wert abfragen.

Tabelle 3.1 Controls, die das Ereignis »Value Changed« senden

Klasse Wert Beschreibung

UIDatePicker

date

Erlaubt die Auswahl eines Datums, einer Uhrzeit oder von beidem auf einmal. Außerdem kann dieser View einen animierten Countdown anzeigen.

UIPageControl

currentPage

Erlaubt die Navigation zwischen verschiedenen Seiten über kleine Punkte wie im Springboard.

UIRefreshControl

refreshing

Stellt Tableviews eine Standardfunktionalität zum Erneuern der Zellinhalte bereit.

UISegmentedControl

selectedSegmentIndex

Stellt mehrere Buttons nebeneinander dar, von denen der Nutzer aber nur einen auswählen kann (Radiobutton-Leiste).

UISlider

value

Erlaubt die nahezu stufenlose Auswahl eines Wertes über einen Schieberegler.

UISwitch

on

ein Schalter mit zwei stabilen Zuständen

UIStepper

value

Erlaubt die Auswahl eines Wertes über einen Plus- und einen Minusknopf. Dieses Control gibt es seit iOS 5.

Sie können also bei diesen UI-Elementen genauso verfahren wie bei dem Schalter im vorangegangenen Abschnitt, um eine Methode bei Wertänderung aufzurufen: Ziehen Sie einfach im Interface Builder eine Action-Verbindung vom Control zum Viewcontroller, und implementieren Sie die Action-Methode mit dem gewünschten Code.

Ereignisse für Gesten

Ein wichtiger Erfolgsfaktor des iPhones ist seine Gestensteuerung. Es gibt in iOS zwei Klassen von Gesten. Der Nutzer kann über Berührungsgesten Eingaben auf dem Bildschirm machen. Das können einfache Fingerdrücke oder komplexere Mehrfingerbewegungen auf dem Touchscreen sein. Dieser Abschnitt beschäftigt sich ausschließlich mit dieser Gestenklasse. Eine weitere grundsätzliche Möglichkeit, Eingaben zu tätigen, sind Bewegungs- und Schüttelgesten, die wir in Kapitel 6, »Models, Layer, Animationen«, behandeln.

Berührungsgesten lösen eine Reihe von unterschiedlichen Ereignissen aus. Eine Berührungsgeste kann sich über mehrere Views erstrecken. Die Ereignisse werden hingegen immer an das Control gesendet, das das erste Ereignis einer Geste empfangen hat.

  • UIControlEventTouchDown sendet das Control bei der ersten Berührung und leitet alle Gesten ein.
  • UIControlEventTouchDownRepeat sendet das Control bei mehreren schnell aufeinanderfolgenden Berührungen nach UIControlEventTouchDown. Sie können mit diesem Ereignistyp beispielsweise Doppel- oder Dreifach-Taps auswerten. Die Anzahl der Taps können Sie über die Property tapCount aus den Touchobjekten des Ereignisses erfragen. Die Touchobjekte erhalten Sie über die Property allTouches der Klasse UIEvent (siehe Listing 3.32).
  • UIControlEventTouchUpInside beendet eine Geste, wenn sich der Finger zuletzt innerhalb des auslösenden Controls befunden hat. Sie verwenden diesen Ereignistyp in der Regel als Aktionsauslöser bei Buttons.
  • UIControlEventTouchUpOutside beendet eine Geste, wenn sich der Finger zuletzt außerhalb des auslösenden Controls befunden hat. Sie verwenden dieses Ereignis in der Regel, um den Abbruch einer Geste durch den Nutzer zu verarbeiten.
  • UIControlEventTouchCancel bricht eine Geste ab. Dieser Ereignistyp wird beispielsweise an Controls innerhalb eines Scrollviews gesendet, wenn Cocoa Touch erkennt, dass Sie mit der Geste scrollen möchten und die Ereignisse nicht das Control betreffen. Cocoa Touch sendet dieses Ereignis auch, wenn es die Gestenverarbeitung wegen einer Speicherwarnung abbrechen muss.
  • UIControlEventTouchDragInside zeigt eine Fingerbewegung innerhalb des Controls an.
  • UIControlEventTouchDragOutside zeigt eine Fingerbewegung außerhalb des Controls an.
  • UIControlEventTouchDragExit sendet das Control, wenn Sie die Finger aus dem Control herausbewegen. Dieser Ereignistyp markiert den Übergang von UIControlEventTouchDragInside zu UIControlEventTouchDragOutside.
  • UIControlEventTouchDragEnter sendet das Control beim Verschieben des Fingers in das Control hinein. Dieser Ereignistyp markiert den Übergang von UIControlEventTouchDragOutside zu UIControlEventTouchDragInside.

Sie können sich sehr komplexe Gesten ausdenken und mit ihnen Ereignistypen umsetzen. Allerdings scheinen die von außerhalb des Controls gesendeten Ereignisse nicht so zuverlässig zu funktionieren. Es kann beispielsweise vorkommen, dass Sie bei einer Fingerverschiebung aus dem Control heraus noch weitere UIControlEventTouchDragInside-Ereignisse empfangen. Die Umschaltung auf UIControlEventTouchDragOutside erfolgt teilweise erst, wenn sich der Finger schon lange außerhalb des Controls befindet. Wenn Sie also diese Ereignistypen verwenden möchten, sollten Sie ausgiebige Tests auf Geräten mit einplanen.

Viele Gesten lassen sich indes relativ einfach umsetzen, und Sie können beispielsweise Mehrfach-Taps mit folgender Action-Methode auswerten, die Sie mit dem Ereignistyp UIControlEventTouchDownRepeat verbinden:

- (IBAction)handleMultiTap:(id)inSender 
forEvent:(UIEvent *)inEvent {
UITouch *theTouch = inEvent.allTouches.anyObject;
if(theTouch.tabCount == 2) {
// Doppel-Tap erkannt
}
}

Listing 3.32 Erkennung einer Double-Tap-Geste

In den Body der if-Abfrage können Sie den Code einsetzen, den Sie bei einem Doppel-Tap ausführen wollen. Die Zahl in der Bedingung gibt die notwendige Anzahl der Taps an. Wenn Sie sie entsprechend anpassen, können Sie mit dem Code auch Dreifach- oder Vierfach-Taps erkennen.

Jede Geste sendet mindestens zwei Ereignisse: UIControlEventTouchDown und einen Touch-up-Event. Sie können bei Gesten für Fingerbewegungen UIControlEventTouchDown zur Initialisierung der Geste und UIControlEventTouchUpInside oder -Outside zum Beenden verwenden. Gegebenenfalls sollten Sie auch das Ereignis UIControlEventTouchCancel auswerten. Über dieses Ereignis können Sie Änderungen bei einem Gestenabbruch rückgängig machen.

Die Gestenverarbeitung für Ereignisse lässt sich jedoch nur sehr schlecht wiederverwenden. Das ist gerade bei komplexen Gesten ungünstig. Für die Gestenverarbeitung gibt es noch andere Möglichkeiten (siehe Abschnitt 3.2.8, »Touch-A, Touch-A, Touch Me«).

Ereignisse für Textänderungen

Diese Ereignisse unterstützt derzeit nur die Klasse UITextField, die ein Feld für die Eingabe eines einzeiligen Textes darstellt. Sie können zwar auch hier Action-Methoden mit UIEvent-Parametern verwenden. Für diesen Parameter bekommen Sie allerdings immer nil übergeben.

  • UIControlEventEditingDidBegin markiert den Beginn der Eingabe in das Textfeld.
  • UIControlEventEditingDidChange zeigt die Änderungen des Textes im Textfeld an.
  • UIControlEventEditingDidEnd markiert das Ende der Eingabe in das Textfeld.
  • UIControlEventEditingDidEndOnExit markiert ebenfalls das Ende der Eingabe in das Textfeld. Das Textfeld sendet dieses Ereignis anscheinend nur, wenn der Nutzer das Textfeld durch Drücken des Return-Knopfes über die Systemfunktion beendet. Leider geht die Apple-Dokumentation auf dieses Ereignis nicht genauer ein.

Ein View, dessen Klasse keine Unterklasse von UIControl ist, kann keine Ereignisse verschicken. Wenn Sie indes ein Ereignis senden möchten, beispielsweise um die Berührung eines Bildes zu verarbeiten, können Sie das Bild einfach in einen View mit der Klasse UIControl legen. Sie legen ein solches Control wie einen Clockview an. Ziehen Sie ein Viewobjekt an die gewünschte Stelle, und ändern Sie seine Klasse im Identitätsinspektor in UIControl. Im Verbindungsinspektor dieses Views finden Sie dann alle beschriebenen Ereignisse. Alternativ können Sie auch einen Button verwenden, der auch die Anzeige von Bildern unterstützt.


Rheinwerk Computing - Zum Seitenanfang

3.2.7Controlzustände und ButtonsZur nächsten ÜberschriftZur vorigen Überschrift

Neben den Ereignissen unterstützen Controls auch drei boolesche Systemzustände. Diese Zustände werden durch eine Bitmaske in der Property state des Controls abgebildet.

  • UIControlStateHighlighted ist aktiv, solange der Nutzer das Control gedrückt hält. Diesen Zustand können Sie auch über die Property highlighted abfragen oder setzen.
  • UIControlStateDisabled setzt das Control inaktiv. Es verarbeitet keine Eingaben und versendet auch keine Ereignisse. Diesen Zustand können Sie auch über die Property enabled abfragen oder setzen. Der Wert der Property ist dabei jedoch genau umgekehrt zu dem Wert in der Bitmaske.
  • UIControlStateSelected zeigt den Auswahlzustand des Controls an. Diesen Zustand können Sie auch über die Property selected abfragen oder setzen.

Ein Control kann mehrere Zustände gleichzeitig aktiviert haben. Wenn kein Zustand in der Bitmaske gesetzt ist, hat das Control den Zustand UIControlStateNormal. Am ausgiebigsten machen Buttons von den Zuständen Gebrauch.

Buttons sind relativ komplexe Controls, die mehrere Darstellungselemente unterstützen. Die Darstellungselemente können Sie in Abhängigkeit von den Zuständen setzen. Sie können für jede Zustandskombination eine eigene Darstellung festlegen. Zusätzlich können Sie über die Property adjustsImageWhenHighlighted festlegen, dass der Button beim Drücken noch einen Glüheffekt anzeigt. Abbildung 3.30 stellt rechts jeweils einen Button im Zustand highlighted ohne und mit Glüheffekt dar.

Abbildung

Abbildung 3.30 Die Button-Typen unter iOS 6 und iOS 7

Ein Button kann einen von sechs vordefinierten Darstellungstypen haben, wobei fünf Typen ein festes Aussehen haben (siehe Abbildung 3.30) und Sie beim sechsten Typ, UIButtonTypeCustom, das Aussehen selbst bestimmen können. Unter iOS 7 haben die Info-Buttons und der Disclosure-Button das gleiche Aussehen; das ist kein Fehler, sondern von Apple so gewollt. Da der Rounded-Rect-Button unter iOS 7 kein abgerundetes Rechteck mehr zeigt, hat Apple dafür die Konstante UIButtonTypeSystem eingeführt und den Typ UIButtonTypeRoundedRect als veraltet markiert.

Ein Custom-Button unterstützt drei Darstellungselemente: ein Vorder- und ein Hintergrundbild sowie einen Titel (siehe Abbildung 3.31). Für den Titel können Sie außerdem die Text- und die Schattenfarbe zustandsabhängig setzen.

Abbildung

Abbildung 3.31 Die Darstellungselemente eines Buttons

Sie können also über diese fünf Eigenschaften das wesentliche Aussehen des Buttons festlegen, wobei Sie das Vordergrundbild und den Titel über Insets – das sind Innenabstände – beliebig positionieren können, entweder über die Property imageEdgeInsets oder die Property titleEdgeInsets. Über die Property contentEdgeInsets können Sie außerdem den kompletten Inhalt – also Bild und Titel – auf einmal positionieren. Diese Einstellungen können Sie auch über den Interface Builder (siehe Abbildung 3.32, unterer Bereich) vornehmen, wobei Sie den Typ über das Menü Edge auswählen und die Werte unter Inset eintragen.

Abbildung

Abbildung 3.32 Der Attributinspektor eines Buttons

Wenn Sie für eine Zustandskombination eine Darstellungseigenschaft nicht definieren, wird stattdessen die des Normalzustands verwendet. Im Interface Builder können Sie alle Eigenschaften für die einfachen Zustände, aber nicht für Kombinationen aus mehreren Zuständen festlegen. Sie können also beispielsweise das Hintergrundbild für den Zustand selected festlegen, hingegen nicht für selected + highlighted; das geht nur über Programmcode.

Durch die Möglichkeit, für die verschiedenen Button-Zustände unterschiedliche Darstellungen festzulegen, können Sie Ihren Apps relativ einfach optisch ansprechende Buttons hinzufügen und damit auch fehlende Button-Typen (beispielsweise Checkboxen) basteln; so lässt sich die Uhr auch über einen einrastenden Button anstatt eines Schalters steuern. Das Einrasten erreichen Sie durch die Invertierung des Zustands selected.

Legen Sie im Interface Builder einen Button an, und stellen Sie über dessen Attributinspektor für die Zustände Default, Highlighted und Selected die Titel »ein«, »klick« beziehungsweise »aus« ein. Danach erzeugen Sie für den Button eine neue Action-Methode mit dem Namen toggleAnimation:, so wie Sie das für den Schalter gemacht haben. Achten Sie darauf, dass Sie diesmal die Action-Methode mit dem Ereignis Touch Up Inside verbinden, was der Interface Builder allerdings als Standard vorgibt. Der Button sendet dieses Ereignis, wenn der Finger den Button losgelassen hat.

- (IBAction)toggleAnimation:(UIButton *)inSender {
inSender.selected = !inSender.selected;
if(inSender.selected) {
[self startAnimations];
}
else {
[self stopAnimations];
}
self.animationSwitch.on = inSender.selected;
}

Listing 3.33 Action-Methode für einen einrastenden Button

Die Implementierung der Action-Methode finden Sie in Listing 3.33; dabei dient die letzte Zeile dazu, den Schalter auf den aktuellen Zustand zu aktualisieren. Analog dazu sollten Sie die Methode switchAnimation: um die Anweisung

self.animationButton.selected = inSender.on;

am Ende erweitern. Dazu müssen Sie jedoch zunächst ein Outlet namens animationButton auf den Button anlegen.

Nach dem Start des Programms ist die Uhr ausgeschaltet, und der Button zeigt die Beschriftung ein. Wenn Sie den Button drücken, ändert sich die Beschriftung in klick und nach dem Loslassen in aus. In diesem Zustand verbleibt der Button, und die Uhr läuft.

Durch erneutes Drücken des Buttons ändert sich die Beschriftung wieder in ein. Der Button hat doch den Zustand highlighted; warum erscheint hier nicht auch die Beschriftung klick? Das liegt daran, dass in diesem Fall die Zustände highlighted und selected gesetzt sind. Für diese Zustandskombination haben Sie jedoch keinen Titel festgelegt, so dass der Button den Titel des Zustands default verwendet.

Den Titel für diese Zustandskombination können Sie, wie bereits erwähnt, nicht im Interface Builder festlegen, sondern müssen das im Programmcode machen. Dafür legen Sie ein Outlet auf den Button an. Die Klasse UIViewController besitzt eine weitere Methode, viewDidLoad, die Cocoa Touch aufruft, nachdem der Viewcontroller den View geladen hat. In dieser Methode können Sie die Werte für diese Zustandskombination festlegen.

Da der Button für den Zustand selected + highlighted den gleichen Titel wie für den Zustand selected verwenden soll, kopiert die Methode den entsprechenden Titel aus dem Button. Dadurch können Sie den Titel allein über den Interface Builder pflegen. Durch diese Änderung zeigt der Button in der oben beschriebenen Situation jetzt auch den Titel klick anstatt ein an.

- (void)viewDidLoad {
[super viewDidLoad];
NSString *theTitle = [self.animationButton
titleForState:UIControlStateHighlighted];
[self.animationButton setTitle:theTitle forState:
UIControlStateSelected | UIControlStateHighlighted];
}

Listing 3.34 Initialisierung eines Buttons nach dem Laden des Views


Rheinwerk Computing - Zum Seitenanfang

3.2.8Touch-A, Touch-A, Touch MeZur nächsten ÜberschriftZur vorigen Überschrift

Wie bereits erwähnt, können Sie den Target-Action-Mechanismus für die Gestenverarbeitung einsetzen. Dieses Vorgehen ist indes recht aufwendig, wenn Sie eine Geste wiederverwenden möchten. Cocoa Touch bietet noch andere Möglichkeiten, Gesten zu verarbeiten.

Damit ein View die Nachrichten für die Touch-Verarbeitung empfängt, müssen Sie immer seine Property userInteractionEnabled setzen. Cocoa Touch durchsucht in der View-Hierarchie nur diese Views und sendet die Nachrichten an den tiefsten View, unter dem der Berührungspunkt des Fingers liegt. Mit »der tiefste View« ist gemeint, dass es in diesem View keinen weiteren, direkten Subview an der Fingerposition mit gesetzter Property userInteractionEnabled gibt.

Der View muss auch wollen

Sie können über userInteractionEnabled die Touch-Verarbeitung komplett ausschalten. Wenn Sie diesen Property-Wert also auf NO setzen, reagieren weder der View noch dessen Subviews mehr auf Berührungen. Dabei ist es egal, ob Sie ein Control verwenden oder die Touches direkt oder über eine andere Methodik auswerten. Bei userInteractionEnabled = NO ignoriert der View Ihre Berührungen – mögen Sie ihn auch noch so zärtlich streicheln.

Die Klasse UIView stellt zwei Methoden bereit, um den Subview zu ermitteln, der die Gesten verarbeitet. Die Methode hitTest:withEvent: bestimmt zu einem Punkt den Subview, der die Gestenverarbeitung übernehmen soll. Sie verwendet dazu die Methode pointInside:withEvent:, die entscheidet, ob der angegebene Punkt in dem View liegt oder nicht. Während der Event-Parameter bei beiden Methoden nil sein darf, müssen Sie den Punkt relativ zu dem Koordinatensystem des Views angeben. Wenn Sie diese Methode aus dem Programmcode aufrufen, müssen Sie also in der Regel den Punkt erst umrechnen.

Die Klasse UIView stellt dafür die Methoden convertPoint:fromView: und convertPoint:toView: bereit. Die erste Methode rechnet den Punkt aus dem Koordinatensystem des zweiten Parameters in das Koordinatensystem des Nachrichtenempfängers um. Bei der zweiten Methode sind die Koordinatensysteme des zweiten Parameters und des Empfängers bei der Berechnung vertauscht.

... und raus bist du!

Wenn Sie die Viewbestimmung für die Gestenverarbeitung anpassen möchten, sollten Sie in Ihren Views die Methode pointInside:withEvent: überschreiben. Ein Überschreiben der Methode hitTest:withEvent: ist in der Regel nicht notwendig.

Sofern Sie den Property-Wert clipsToBounds eines Views auf NO setzen, kann jeder Subview des Views über dessen Grenzen hinaus zeichnen. Allerdings empfängt der Subview nur da Touches, wo er auch innerhalb seiner umgebenden Views liegt. Diese Situation stellt Abbildung 3.33 dar: Cocoa Touch stellt den Subview, der in der Hierarchie im Superview liegt, zwar komplett auf dem Bildschirm dar; er empfängt jedoch nur Ereignisse für die Gesten in dem gekennzeichneten, berührungsempfindlichen Bereich. Die anderen Berührungen erhält in diesem Beispiel der Hauptview.

Abbildung

Abbildung 3.33 Berührungsempfindlicher Bereich eines Subviews

Mit pointInside:withEvent: können Sie also festlegen, in welchen Bereichen ein View auf Berührungen reagiert. Die eigentliche Auswertung der Berührungen kann auf unterschiedlichen Wegen geschehen.

Gestenverarbeitung über Responder

Die Klasse UIResponder ist die direkte Oberklasse von UIView und UIViewController. Sie stellt mehrere Methoden zur Auswertung von Gesten bereit. Die Methoden für die Berührungsgesten haben jeweils zwei Parameter und liefern void zurück. Der erste Parameter hat die Klasse NSSet und ist eine Menge der von UITouch-Objekten, die die Fingerberührungen auf dem Bildschirm repräsentieren. Der zweite Parameter ist ein UIEvent-Objekt.

Der Methodenaufruf von touchesBegan:withEvent: leitet eine Gestensequenz ein. Die folgenden Fingerbewegungen erhält der Responder über die Methode touchesMoved:withEvent:. Abgeschlossen wird die Geste über einen Aufruf von entweder touchesEnded:withEvent: oder touchesCancelled:withEvent:. Dabei kennzeichnet ein Aufruf der Methode touchesCancelled:withEvent:, dass das System eine Geste abbrechen möchte (siehe dazu die Beschreibung zu UIControlEventTouchCancel in Abschnitt 3.2.6, »Ereignisse«).

Diese Methoden sind die Grundlagen der Gestenverarbeitung. Allerdings führt die Verwendung dieser Methoden in der Regel zu einer relativ festen Koppelung von View und Controller, da der View meistens das Ergebnis einer Geste an den Controller weiterreichen muss. Falls Sie dennoch eine eigene Gestenverarbeitung über diese Responder-Methoden realisieren möchten, sollten Sie die Kommunikation mit dem Controller über Delegation realisieren. Eine direkte Verwendung von Controller-Eigenschaften im View ist eine Verletzung des MVC-Architekturmusters, weil dadurch eine Abhängigkeit des Views von der Controller-Schicht entsteht.

Responder-Methoden und Delegation

In Kapitel 2, »Die Reise nach iOS«, haben Sie bereits die Delegation als Muster kennengelernt. Um den View mit der Controller-Schicht lose zu koppeln, erstellen Sie ein Protokoll, das die Delegate-Methoden deklariert. Außerdem spendieren Sie dem View eine Property, um ihn mit dem Delegate verknüpfen zu können. Dann können die Responder-Methoden des Views immer eine Delegate-Methode aufrufen, wenn sie die gewünschte Geste erkannt haben. Sie sollten also nicht die Events einfach weiterleiten, sondern lieber die erkannten Gesten.

Angenommen, Ihr View soll Kreisbewegungen erkennen, dann könnte das Protokoll die Delegate-Methode circleView:didDetectsCircleWithCenter:radius: bereitstellen, die der View nach der Erkennung eines Kreises aufruft. Wenn Sie diese Geste aber in unterschiedlichen Views erkennen möchten, sollten Sie dafür lieber einen Gesture-Recognizer einsetzen.

Gestenverarbeitung über Controls

Eine andere Möglichkeit, einen Controller lose an einen View zu koppeln, ist die Verwendung des Target-Action-Mechanismus über eigene Unterklassen von UIControl. Diese Klasse stellt ebenfalls vier Methoden zur Gestenverarbeitung bereit, die den Methoden der Klasse UIResponder sehr ähneln. Ihre Namen lauten beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent: und endTrackingWithTouch:withEvent:, und sie erhalten jeweils zwei Parameter, wovon der erste ein Touchobjekt und der zweite der Event ist. Im Gegensatz zu den Responder-Methoden erhalten Sie hier also nicht alle Berührungen des Bildschirms als direkte Parameter. Falls Sie eine Mehrfingergeste implementieren möchten, können Sie jedoch alle Berührungen über die Methode allTouches des Events abfragen. Außerdem gibt es die Methode cancelTrackingWithEvent:, die das Control analog zu touchesCancelled:withEvent: beim Abbruch einer Geste aufruft.

Kehren wir zurück zum Projekt AlarmClock: Über die Gestenverarbeitung soll aus der Uhr ein Wecker entstehen. Bei einem analogen Wecker gibt es neben den Zeigern für die Zeitanzeige einen weiteren Zeiger für die Alarmzeit. In der Wecker-App soll der Nutzer diesen Zeiger durch Berührung und Bewegung mit dem Finger einstellen können. Das Control muss also eine Drehbewegung mit einem Finger auf dem Bildschirm erkennen. Diese Bewegung ähnelt dem Wählen auf einem Telefon mit Wählscheibe. Das Control soll dabei auch die Darstellung des Alarmzeigers übernehmen. Legen Sie zunächst eine neue Klasse ClockControl als Unterklasse von UIControl im Projekt AlarmClock an.

Das Control erhält für die Darstellung der Alarmzeit die Property time mit dem Typ NSTimeInterval, der eine Zeitdauer in Sekunden als Fließkommawert enthält. Sie können die Alarmzeit über die Methode angle in einen Winkel umrechnen und entsprechend über die Methode setAngle: als Winkelwert setzen. Dabei sind 12 Stunden genau

12 × 60 × 60 = 43200 Sekunden

und die entsprechen einem Winkel von 360°, und damit können Sie aus der Zeit t über

2tπ / 43200 = tπ / 21600 = w

den Winkel w im Bogenmaß berechnen. Wenn Sie die letzte Gleichung mit 21600 / π multiplizieren, erhalten Sie die Gleichung

t = 21600w / π

um die Zeit aus dem Winkel zu berechnen. Diese beiden Gleichungen bilden die Grundlage für die Implementierung der Methoden angle und setAngle:

- (CGFloat)angle {
return self.time * M_PI / 21600.0;
}

- (void)setAngle:(CGFloat)inAngle {
self.time = 21600.0 * inAngle / M_PI;
}

Listing 3.35 Umrechnung der Alarmzeit in einen Winkel und umgekehrt

Da auch das Control die Methoden midPoint und pointWithRadius:angle: benötigt, lagern Sie diese Methoden aus der Klasse ClockView in die Kategorie UIView(AlarmClock) aus. Rufen Sie dazu den Menüpunkt FileNewFile... auf, und wählen Sie die Vorlage Objective-C Category in der Rubrik iOSCocoa Touch aus. Wenn Sie im folgenden Dialog »AlarmClock« unter Extension Name und »UIView« unter Class eingeben (siehe Abbildung 3.34), legt Xcode beim Speichern die Dateien UIView+AlarmClock.h und UIView+AlarmClock.m an.

In die Headerdatei tragen Sie die Deklarationen für die beiden Methoden midPoint und pointWithRadius:angle: ein. Die komplette Datei sehen Sie in Listing 3.36.

#import <UIKit/UIKit.h>

@interface UIView (AlarmClock)

- (CGPoint)midPoint;
- (CGPoint)pointWithRadius:(CGFloat)inRadius
angle:(CGFloat)inAngle;

@end

Listing 3.36 Deklaration der Kategorie

Abbildung

Abbildung 3.34 Anlegen einer Kategorie für die Wecker-App

Analog verschieben Sie die Implementierungen dieser beiden Methoden aus der Klasse ClockView in den Implementierungsblock der Kategorie in der Datei UIView+AlarmClock.m. Durch das Entfernen der Methoden zeigt Xcode in der Klassenimplementierung von ClockView nun Fehler an. Um sie zu beheben, importieren Sie die Kategorie in die Datei ClockView.m. Dazu fügen Sie die Anweisung #import "UIView+AlarmClock.h" bei den anderen Importanweisungen in ClockView.m ein. Damit Sie die Methoden auch im neuen Control verwenden können, fügen Sie die Importanweisung außerdem an den Anfang von ClockControl.m ein.

Zu der Fingerposition muss das Control den Winkel und damit die Uhrzeit für den Alarmzeiger ermitteln. Dazu dient die Methode angleWithPoint:, die über die C-Funktion atan2 zu einem Punkt im View den Winkel der Linie berechnet, die vom Mittelpunkt zu diesem Punkt führt:

- (CGFloat)angleWithPoint:(CGPoint)inPoint {
CGPoint theCenter = [self midPoint];
CGFloat theX = inPoint.x – theCenter.x;
CGFloat theY = inPoint.y – theCenter.y;
CGFloat theAngle = atan2f(theX, -theY);

return theAngle < 0 ? theAngle + 4.0 * M_PI : theAngle;
}

Listing 3.37 Berechnung des Winkels zu einem Punkt

Fügen Sie diese Methode zu der Implementierung der Kategorie UIView(AlarmClock) hinzu, und erweitern Sie deren Headerdatei um die entsprechende Methodendeklaration:

- (CGFloat)angleWithPoint:(CGPoint)inPoint;

Der Winkel, den die Funktion atan2 berechnet, liegt zwischen –π und π. Negative Werte müssen Sie also durch die Addition von (= Kreisumfang) in entsprechende positive Werte umrechnen. Abbildung 3.35 stellt diese beiden Fälle grafisch dar. Dabei befinden sich positive Werte auf der rechten und negative auf der linken Kreishälfte.

Abbildung

Abbildung 3.35 Umrechnung von Punkten in Winkel

Der Zeiger soll sich jedoch nur verstellen lassen, wenn die Fingerberührung in der Nähe des Zeigers stattfindet. Das erreichen Sie am einfachsten, indem Sie die Methode pointInside:withEvent: überschreiben:

- (BOOL)pointInside:(CGPoint)inPoint
withEvent:(UIEvent *)inEvent {
CGFloat theAngle = [self angleWithPoint:inPoint];
CGFloat theDelta = fabs(theAngle – self.angle);
return theDelta < 4.0 * M_PI / 180.0;
}

Listing 3.38 Prüfung, ob ein Punkt in der Nähe des Alarmzeigers liegt

Die Variable theDelta enthält den Winkelabstand des Fingers vom Zeiger. Wenn dieser Abstand kleiner als 4° (entspricht 2°π / 180° im Bogenmaß) ist, soll das Control den Touch verarbeiten. Mit diesen Methoden können Sie nun die Gestenverarbeitung implementieren. Der Inhalt der drei Methoden beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent: und endTrackingWithTouch:withEvent: ist dabei fast gleich, so dass das Control für diesen Code sinnvollerweise die Hilfsmethode updateAngleWithTouch: bereitstellt. Sie ermittelt jeweils aus der Fingerposition den Punkt im Control und rechnen ihn über die Methode angleWithPoint: in den entsprechenden Winkel um. Diesen Winkel übergibt sie dann an die Methode setAngle:, die daraus die entsprechende Alarmzeit berechnet. Die letzte Anweisung der Methode versendet schließlich das Value-Changed-Ereignis. Sie ruft also bei den entsprechenden Targets des Controls die jeweilige Action-Methode auf.

- (void)updateAngleWithTouch:(UITouch *)inTouch {
CGPoint thePoint = [inTouch locationInView:self];
self.angle = [self angleWithPoint:thePoint];

[self sendActionsForControlEvents:
UIControlEventValueChanged];
}

Listing 3.39 Aktualisierung der Daten in den Tracking-Methoden

Nach der Änderung der Alarmzeit muss Cocoa Touch in der Regel den View neu zeichnen; also sollte eine Änderung des Property-Wertes von time die Nachricht setNeedsDisplay an das Control senden. Dafür gibt es zwei geeignete Vorgehensweisen. Entweder implementieren Sie den Setter setTime:, oder Sie verwenden Key-Value-Observing. Bei der Implementierung von setTime: können Sie auf das Attribut über den Namen _time zugreifen:

- (void)setTime:(NSTimeInterval)inTime {
_time = inTime;
[self setNeedsDisplay];
}

Listing 3.40 Neuzeichnen des Zeigers bei Änderung der Alarmzeit erzwingen

Die Lösung über Key-Value-Observing ist etwas aufwendiger; zunächst einmal müssen Sie die Observierung anmelden, wozu die Methode setup in Listing 3.41 dient, die wiederum die Methoden initWithFrame: und awakeFromNib verwenden. Die Observierungsmethode observeValueForKeyPath:ofObject:change:context: sendet dabei nur einfach an sich selbst. Schließlich sollten Sie nicht vergessen, die Observierung in der Methode dealloc zu beenden. Durch die Methoden in Listing 3.41 zeichnet Cocoa Touch nun ebenfalls den View jedes Mal neu, wenn das Programm den Wert von time aktualisiert.

Diese Variante ist zugegebenermaßen aufwendiger als die Implementierung des Setters. Bei Views mit sehr vielen Propertys erweist sich dieser Weg sehr häufig als praktischer, da für jede weitere Property einfach nur jeweils eine Anweisung in setup und dealloc hinzukommt.

- (id)initWithFrame:(CGRect)inFrame {
self = [super initWithFrame:inFrame];
if (self) {
[self setup];
}
return self;
}

- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}

- (void)dealloc {
[self removeObserver:self forKeyPath:@"time"];
}

- (void)setup {
[self addObserver:self forKeyPath:@"time" options:0 context:nil];
}

- (void)observeValueForKeyPath:(NSString *)inKeyPath
ofObject:(id)inObject change:(NSDictionary *)inChange
context:(void *)inContext {
[self setNeedsDisplay];
}

Listing 3.41 Neuzeichnen des Zeigers über KVO auslösen

Die Methode cancelTrackingWithEvent: sollte den Ausgangszustand des Controls bei Gestenabbruch wiederherstellen. Dazu muss es den aktuellen Winkel zu Beginn der Gestenverarbeitung speichern und in dieser Methode wieder an setAngle: übergeben. Für die Speicherung verwendet das Control eine private Property savedAngle; legen Sie also eine anonyme Kategorie für die Klasse ClockControl mit der Property-Deklaration darin an:

@interface ClockControl()

@property (nonatomic) CGFloat savedAngle;

@end

Listing 3.42 Private Property zum Speichern der vorherigen Alramzeit

Damit können Sie nun die Methoden für die Gestenverarbeitung implementieren:

- (BOOL)beginTrackingWithTouch:(UITouch *)inTouch 
withEvent:(UIEvent *)inEvent {
self.savedAngle = self.angle;
[self updateAngleWithTouch:inTouch];
return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)inTouch
withEvent:(UIEvent *)inEvent {
[self updateAngleWithTouch:inTouch];
return YES;
}

- (void)endTrackingWithTouch:(UITouch *)inTouch
withEvent:(UIEvent *)inEvent {
[self updateAngleWithTouch:inTouch];
}

- (void)cancelTrackingWithEvent:(UIEvent *)inEvent {
self.angle = self.savedAngle;
}

Listing 3.43 Die Methoden für die Gestenverarbeitung

Jetzt fehlt dem Control nur noch die Implementierung der Methode drawRect:, die einfach nur einen farbigen Strich als Zeiger zeichnet. Unter iOS 7 verwendet die Methode für die Zeigerfarbe die Tint-Color des Views und unter älteren Versionen Blau. Dabei benutzt sie während der Gestenverarbeitung eine abgewandelte Farbe als Rückmeldung für den Nutzer. Dieser Zustand lässt sich über die Property tracking der Klasse UIControl erfragen, und die Abwandlung der Farbe erfolgt über einen anderen Alphawert. Dazu verwenden Sie die Methode colorWithAlphaComponent: der Klasse UIColor, die zu einer Farbe eine neue Farbe mit den gleichen Farbkomponenten und dem angegebenen Alphawert erzeugt.

- (void)drawRect:(CGRect)inRectangle {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect theBounds = self.bounds;
CGPoint theCenter = [self midPoint];
CGFloat theRadius = CGRectGetWidth(theBounds) / 2.0;
CGPoint thePoint = [self pointWithRadius:theRadius * 0.7
angle:self.time * M_PI / 21600.0];
UIColor *theColor =
[self respondsToSelector:@selector(tintColor)] ?
self.tintColor : [UIColor colorWithRed:0.0
green:0.0 blue:1.0 alpha:1.0];

if(self.tracking) {
theColor = [theColor colorWithAlphaComponent:0.5];
}
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext,
theColor.CGColor);
CGContextSetLineWidth(theContext, 8.0);
CGContextSetLineCap(theContext, kCGLineCapRound);
CGContextMoveToPoint(theContext,
theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext,
thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}

Listing 3.44 Zeichnen des Alarmzeigers

In das Storyboard können Sie jetzt das Control in den Clockview legen, wenn Sie dem Control eine transparente Hintergrundfarbe geben. Ziehen Sie dazu zuerst ein UIView-Objekt aus der Bibliothek auf das Ziffernblatt, und weisen Sie ihm über den Identitätsinspektor die Klasse ClockControl zu. Das Control sollte die gleiche Größe wie der Clockview haben. Außerdem müssen Sie die Property userInteractionEnabled im Attributinspektor auf YES setzen, damit Cocoa Touch die Gesten auch an das Control sendet. Außerdem verbinden Sie das Control über ein Outlet mit dem Namen clockControl mit dem Viewcontroller; dabei müssen Sie darauf achten, dass Sie vorher das Control im Interface Builder ausgewählt haben, damit der Interface Builder auch den richtigen View für die Verbindung auswählt. Außerdem müssen Sie den Header ClockControl.h importieren. Um zusätzlich die genaue Alarmzeit anzuzeigen, legen Sie ein Label in den Hauptview und verbinden es ebenfalls über ein Outlet mit dem Namen timeLabel mit dem Viewcontroller. Diese Änderungen sehen Sie in Listing 3.45.

#import "AlarmClockViewController.h"
#import "ClockView.h"
#import "ClockControl.h"

@interface AlarmClockViewController ()

@property (weak, nonatomic) IBOutlet ClockView *clockView;
@property (weak, nonatomic) IBOutlet

ClockControl *clockControl;
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;

@end

Listing 3.45 Die Outlets für die Verwaltung der Alarmzeit

In dem Label soll der Wecker die Alarmzeit als Text anzeigen. Außerdem verbinden Sie das Control im Interface Builder über den Ereignistyp Value Changed mit der Methode updateTimeLabel des Viewcontrollers, deren Implementierung Sie in Listing 3.46 finden. Sobald Sie den Alarmzeiger bewegen, sendet das Control ja dieses Ereignis. Die Methode schreibt die aktuelle Alarmzeit in das Label. Die Variable theTime enthält dabei die Alarmzeit in Minuten. Die Stunden und Minuten für die Anzeige berechnen Sie daraus durch eine Ganzzahldivision durch 60, wobei der Quotient der Stunden- und der Divisionsrest der Minutenanteil ist.

- (IBAction)updateTimeLabel {
NSInteger theTime = round(self.clockControl.time / 60.0);
NSInteger theMinutes = theTime % 60;
NSInteger theHours = theTime / 60;

self.timeLabel.text = [NSString stringWithFormat:
@"%d:%02d", theHours, theMinutes];
}

Listing 3.46 Erzeugung einer Zeichenkette aus einer Alarmzeit

Wenn Sie den Alarmzeiger in der App bewegen, sehen Sie, dass der Controller auch die angezeigte Alarmzeit kontinuierlich aktualisiert. Dem Wecker fehlt jetzt nur noch die Weckfunktion, also das Klingeln. Außerdem muss der Viewcontroller beim Loslassen des Zeigers den Alarm konfigurieren. Diese beiden Themen behandelt Abschnitt 3.3, »Lokale Benachrichtigungen«, und die Implementierung der Weckfunktion geschieht über die Action-Methode updateAlarm, die Sie über das Ereignis Touch Up Inside mit dem Control verbinden.

Mehrere Ereignistypen behandeln

Der Viewcontroller verarbeitet also zwei unterschiedliche Ereignistypen des Controls. Das ist gerade bei Controls, die kontinuierliche Werte liefern, sinnvoll. Der Ereignistyp Value Changed dient zur Aktualisierung des Views und somit zur interaktiven Rückmeldung des Zustands an den Nutzer, während das Ereignis Touch Up Inside die Verarbeitung des endgültigen Wertes in der App auslöst. Sie können jedoch auch andere Ereignisarten sinnvoll verwenden. Beispielsweise können Sie durch den Ereignistyp Touch Up Outside einen Abbruch der Operation ermöglichen, indem Sie dafür eine Action-Methode implementieren, die den alten Zustand wiederherstellt.

Gestenverarbeitung über Gesture-Recognizer

Eine weitere Möglichkeit der Gestenverarbeitung sind Gesture-Recognizer. Die Klasse UIGestureRecognizer ermöglicht eine Implementierung von Gestensteuerungen, die von den Views getrennt ist. Damit können Sie jede beliebige Gestenverarbeitung zu jedem View hinzufügen. Der Gesture-Recognizer trennt also die Eingabeverarbeitung von der Darstellung des Views. Dadurch sind diese beiden Aufgaben (Darstellung und Ereignisverarbeitung) vollkommen unabhängig voneinander oder auch orthogonal zueinander, und Sie können die entsprechenden Komponenten (Views und Gesture-Recognizer) nahezu beliebig miteinander kombinieren.

Die Klasse UIView bietet zwei Methoden und eine Property zum Verwalten von Gesture-Recognizern an. Mit addGestureRecognizer: fügen Sie einem View einen neuen Gesture-Recognizer hinzu, den Sie mit removeGestureRecognizer: wieder entfernen können. Mit der Property gestureRecognizers können Sie alle Gesture-Recognizers des Views abfragen oder auf einmal setzen. Cocoa Touch bietet bereits eine Reihe von fertigen Gesture-Recognizern an, die Sie auch direkt über die Objektbibliothek (siehe Abbildung 3.36) des Interface Builders zu Ihren Views hinzufügen können, indem Sie sie auf die Views ziehen.

Abbildung

Abbildung 3.36 Die Gesture-Recognizer im Interface Builder

Die Icons in Abbildung 3.36 haben von links nach rechts die folgenden Bedeutungen:

  1. UITapGestureRecognizer erkennt einzelne oder mehrere Berührungen des zugehörigen Views. Sie können sowohl die notwendige Tap-Zahl als auch die Anzahl der beteiligten Finger zur Auslösung vorgeben.
  2. UIPinchGestureRecognizer erkennt die Bewegung von zwei Fingern aufeinander zu oder voneinander weg. Diese Geste wird meistens zum Heraus- oder Hineinzoomen verwendet.
  3. UIRotationGestureRecognizer erkennt die Bewegung von zwei sich umeinander drehenden Fingern, wie sie viele Apps beispielsweise für die Drehung von Bildern einsetzen.
  4. UISwipeGestureRecognizer erkennt Wischbewegungen. Sie können die Anzahl der beteiligten Finger angeben, jedoch damit nur die Richtung und keine Distanzen auswerten. Einige Apps verwenden Wischbewegungen zum Löschen von Inhalten.
  5. UIPanGestureRecognizer erkennt Verschiebe-Bewegungen in einem View. Sie können damit beispielsweise einen View innerhalb eines anderen verschieben (Dragging), da Sie mit diesem Recognizer auch die Richtung und die Distanz der Geste ermitteln können. Auch bei diesem Gesture-Recognizer können Sie die Anzahl der beteiligten Finger vorgeben.
  6. UILongPressGestureRecognizer erkennt längere Berührungen des zugrundeliegenden Views. Diese Gestenauswertung eignet sich sehr gut, wenn ein View unterschiedliche Gestenarten unterstützen soll. Beispielsweise können Sie eine Karte scrollen und durch längeres Drücken eine Markierung setzen. Auch bei diesem Gesture-Recognizer können Sie die Anzahl der beteiligten Finger einschränken. Außerdem können Sie eine minimale Berührungsdauer wählen.

Sie können auch eigene Unterklassen der Klasse UIGestureRecognizer schreiben und so eine eigene Gestenverarbeitung implementieren. Dafür besitzt die Klasse die gleichen vier Methoden, die auch die Klasse UIResponder bereitstellt. Zusätzlich gibt es eine Methode reset. Sie können diese Methode überschreiben, um nach dem Abschluss einer Geste Ihren Gesture-Recognizer für die nächste Geste zurückzusetzen.

Gesture-Recognizer unterstützen wie Controls den Target-Action-Mechanismus – allerdings ohne Ereignistypen. Sie können über die Methode addTarget:action: eine neue Aktion hinzufügen, die Sie mit removeTarget:action: auch wieder entfernen können.

Das Beispielprojekt verwendet einen UILongPressGestureRecognizer, um die Alarmzeit ein- und auszuschalten. Eine Berührung des Ziffernblatts, die länger als vier Zehntelsekunden dauert, aktiviert entweder den Alarm und setzt den Zeiger an die Position des Fingers oder schaltet den Alarm aus und versteckt den Zeiger und das Label mit der Alarmzeit. Wenn Sie einen Longpress-Recognizer aus der Bibliothek wie in Abbildung 3.37 auf den Clockview ziehen, zeigt der Interface Builder immer das schwarze Label mit der Beschriftung Clock Control an und fügt den Recognizer zu dem Clock-Control hinzu. Sie können jedoch den Recognizer zum Clockview hinzufügen, indem Sie ihn auf das entsprechende Objekt in der Baumansicht ziehen.

Abbildung

Abbildung 3.37 Gesture-Recognizer zum Clock-Control hinzufügen

Warum kann der Recognizer nicht zum Clock-Control?

Das hat zwei Gründe: Zum einen begrenzt das Control seine berührungsempfindliche Fläche durch die Implementierung der Methode pointInside:withEvent: aus Listing 3.38. Dadurch reagiert der Recognizer auch nur in diesem Bereich; er soll jedoch die ganze Fläche beobachten.

Zum anderen versteckt der Viewcontroller das Control, um den Alarm auszuschalten. Wenn jedoch der View eines Recognizers unsichtbar ist, dann empfängt der Recognizer auch keine Berührungen.

Diese beiden Probleme lassen sich am einfachsten umgehen, indem Sie den Recognizer zum Clockview hinzufügen.

Als Nächstes ziehen Sie eine Action-Verbindung vom Gesture-Recognizer in der Baumansicht in die anonyme Kategorie des Viewcontrollers und geben ihr den Namen switchAlarm. Wählen Sie beim Anlegen unter Type die Klasse UILongPressGestureRecognizer für den Parameter aus.

Abbildung

Abbildung 3.38 Action-Verbindung für den Gesture-Recognizer erzeugen

Das Ein- und Ausschalten des Alarmzeigers und des Labels erfolgt über die Property alarmHidden mit dem Typ BOOL:

@property (nonatomic) BOOL alarmHidden;

Dabei können Sie den Zustand der Property über die Sichtbarkeit des Controls festlegen, indem Sie die beiden Accessoren der Property implementieren. Die Sichtbarkeit eines Views bestimmen Sie über seine Property hidden.

- (BOOL)alarmHidden {
return self.clockControl.hidden;
}

- (void)setAlarmHidden:(BOOL)inAlarmHidden {
self.clockControl.hidden = inAlarmHidden;
self.timeLabel.hidden = inAlarmHidden;
}

Listing 3.47 Sichtbarkeit der Alarmanzeige steuern

Die Zuordnung ist wichtig

Der Gesture-Recognizer funktioniert nicht richtig, wenn Sie ihn nicht zu dem View, sondern zu dem Control hinzufügen. Sie sollen ihn ja auch bei ausgeschaltetem Alarm benutzen können. Da Sie in diesem Zustand allerdings das Control versteckt haben, beachtet Cocoa Touch dann auch den Recognizer nicht. Das Ziffernblatt ist hingegen immer sichtbar. Sie können die Zuordnung des Recognizers übrigens in seinem Verbindungsinspektor überprüfen.

Die Action-Methode eines Gesture-Recognizers hat entweder keinen oder genau einen Parameter. In diesem Parameter bekommen Sie den Gesture-Recognizer übergeben, und er sollte dessen Typ haben. Die Action-Methode für den Wecker muss die Position des Fingers auslesen. Dazu bietet die Klasse UIGestureRecognizer die Methode locationInView:, mit der Sie die Fingerposition relativ zu dem Koordinatensystem eines Views berechnen können. Die Action-Methode switchAlarm: können Sie jetzt folgendermaßen implementieren:

- (IBAction)switchAlarm: 
(UILongPressGestureRecognizer *)inRecognizer {
if(inRecognizer.state == UIGestureRecognizerStateEnded) {
if(self.alarmHidden) {
CGPoint thePoint =
[inRecognizer locationInView:self.clockView];
CGFloat theAngle =
[self.clockView angleWithPoint:thePoint];
NSTimeInterval theTime = 21600.0 * theAngle / M_PI;

self.alarmHidden = NO;
self.clockControl.time = theTime;
[self updateTimeLabel];
}
else {
self.alarmHidden = YES;
}
}
}

Listing 3.48 Action-Methode zur Aktualisierung des Alarmzeigers

Wenn Sie den Gesture-Recognizer durch eine Berührung auslösen, ruft er die Action-Methode mehrmals auf, jeweils einmal zu Beginn und Ende der Geste. Aus diesem Grund prüft die Methode den Zustand der Geste über die Property state des Gesture-Recognizers. Nur wenn sie den Wert UIGestureRecognizerStateEnded hat und der Nutzer somit die Geste beendet hat, aktualisiert sie die Anzeige für den Alarm.


Rheinwerk Computing - Zum Seitenanfang

3.2.9ÜbergängeZur nächsten ÜberschriftZur vorigen Überschrift

Zusammen mit den Storyboards hat Apple auch Segues (Übergänge) eingeführt. Diese Objekte beschreiben einen Übergang von einem Viewcontroller zu einem anderen. Übergänge haben somit als Verbindungstyp im Interface Builder einen sehr eingeschränkten Anwendungsbereich. Andererseits können Sie darüber schon im Interface Builder eine Verknüpfung zwischen den UI-Elementen und den Views festlegen und somit die Abfolge zwischen den Views visualisieren. Deswegen nennt Apple diese neuen Beschreibungsdateien für Benutzeroberflächen auch Storyboards (Ablaufplan).

Um die Funktionsweise der Übergänge zu veranschaulichen, erstellen Sie ein kleines Projekt. Legen Sie dazu in Xcode ein neues iPad-Projekt des Typs Single View Application an. Öffnen Sie das Storyboard, und legen Sie ein Label, einen Date-Picker und einen Button in den dort View. Ein Date-Picker hat die Klasse UIDatePicker erlaubt die Auswahl eines Datums, einer Uhrzeit oder eines Datums mit einer Uhrzeit. Öffnen Sie den Attributinspektor des Date-Pickers, und wählen Sie unter Mode den Eintrag Date aus (siehe Abbildung 3.39). Danach legen Sie noch ein Outlet namens datePicker in der anonymen Kategorie des Viewcontrollers auf den Date-Picker an.

Abbildung

Abbildung 3.39 View mit Date-Picker

Projektinformation

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

Für den Übergang brauchen Sie einen weiteren Viewcontroller; dazu ziehen Sie das entsprechende Objekt, wie in Abbildung 3.40 gezeigt, auf die Arbeitsfläche. Das Storyboard weist nach dieser Aktion nicht nur zwei Viewcontroller, sondern auch zwei Szenen in der Baumansicht auf, da jeder Viewcontroller auch immer zu genau einer Szene gehört.

Abbildung

Abbildung 3.40 Anlegen eines neuen Viewcontrollers

Für den neuen Viewcontroller brauchen Sie außerdem eine neue Klasse, die Sie analog zu den bisherigen Klassen über das Kontextmenü auf der Gruppe Segue des Projekts anlegen (siehe Abbildung 3.41). Achten Sie im zweiten Schritt darauf, UIViewController als Superklasse auszuwählen, und geben Sie der neuen Klasse den Namen ResultViewController. Nach dem Anlegen öffnen Sie den Identitätsinspektor des neuen Viewcontrollers und weisen ihm unter Class die Klasse ResultViewController zu.

Als Nächstes legen Sie einen Übergang an, indem Sie im Interface Builder bei gedrückter rechter Maustaste ein Gummiband vom Button zu dem neuen Viewcontroller ziehen und in dem schwarzen Dialog den Eintrag modal auswählen (siehe Abbildung 3.42). Mit dieser Auswahl zeigt die App den neuen Viewcontroller als modalen Dialog an: Sie sperrt dadurch den bisherigen Viewcontroller und erlaubt keine Eingaben mehr für ihn, bis Sie den modalen Viewcontroller wieder aus der Ansicht entfernen.

Abbildung

Abbildung 3.41 Die neue Klasse für den Viewcontroller

Abbildung

Abbildung 3.42 Art des Übergangs auswählen

Anschließend legt der Interface Builder zwischen den Viewcontrollern einen Übergang an, den er durch einen grauen Pfeil mit einem Symbol in der Mitte darstellt (siehe Abbildung 3.43). Im Attributinspektor dieses Übergangs können Sie jetzt festlegen, wie Cocoa Touch den Wechsel animieren soll. Wenn Sie unter Transition den Punkt Flip Horizontal auswählen, drehen sich die beiden Views beim Wechsel um ihre vertikale Mittelachse.

Abbildung

Abbildung 3.43 Ein Übergang zwischen zwei Viewcontrollern

Sie können das überprüfen, indem Sie das Projekt im Simulator starten und den Button anklicken. Der Simulator dreht dann die beiden Views wie beschrieben umeinander.

Um vom modalen Viewcontroller wieder zurück zum Segue-Viewcontroller zu gelangen, können Sie allerdings keinen Übergang benutzen, sondern müssen eine Action-Methode verwenden. Legen Sie dazu einen Button in den ResultViewController, und erzeugen Sie für diesen Button eine Action-Methode mit dem Namen dismiss. Die Implementierung dieser Methode besteht aus einem Aufruf:

- (IBAction)dismiss {
[self dismissViewControllerAnimated:YES completion:NULL];
}

Listing 3.49 Modalen Viewcontroller wieder verschwinden lassen

Durch diese Änderung können Sie im Simulator vom Result- wieder zum Segue-Viewcontroller zurückkehren. Dabei drehen sich die Views wieder um die vertikale Mittelsachse; diesmal jedoch in umgekehrter Richtung.

Im nächsten Schritt soll der Segue-Viewcontroller das Datum aus dem Date-Picker an den Result-Viewcontroller übergeben. Dazu legen Sie zunächst in der Deklaration der Klasse ResultViewController eine Property birthDate an. Da der Controller das Datum halten soll, erhält sie den Speicherverwaltungstyp strong.

@interface ResultViewController : UIViewController

@property (nonatomic, strong) NSDate *birthDate;

@end

Listing 3.50 Property für das Datum

In der Methode prepareForSegue:sender: des Ursprungsviewcontrollers – das ist in diesem Fall der Segue-Viewcontroller – können Sie den neu erzeugten Viewcontroller eines Übergangs konfigurieren. Der Übergang ruft diese Methode auf, bevor er den Zielviewcontroller anzeigt. Wenn Sie mehrere Übergänge von einem Viewcontroller aus aufrufen können, können Sie sie anhand ihrer Kennung unterscheiden. Die Kennung können Sie über das Eingabefeld Identifier im Attributinspektor des Übergangs setzen (siehe Abbildung 3.43). Die Unterscheidung im Programmcode kann dann anhand der Property identifier des Übergangs erfolgen, die diese Kennung enthält. Listing 3.51 zeigt Ihnen die Implementierung der Klasse SegueViewController.

- (void)prepareForSegue:(UIStoryboardSegue *)inSegue 
sender:(id)inSender {
if([inSegue.identifier isEqualToString:@"dialog"]) {
ResultViewController *theController =
inSegue.destinationViewController;

theController.birthDate = self.datePicker.date;
}
}
@end

Listing 3.51 Initialisierung eines Controllers bei einem Übergang

Übergänge per Programmcode ausführen

Sie können Übergänge auch im Code über die Methode performSegueWithIdentifier:sender: auslösen. Mit dieser Methode lassen sich also Übergänge auch über Action-Methoden aufrufen. Für den Übergang aus dem Beispielprojekt sieht das so aus:

- (IBAction)performSegue:(id)inSender {
[self performSegueWithIdentifier:@"dialog"
sender:inSender];
}

Bei dem Auslösen bekommen Sie allerdings keine Referenz auf den Zielviewcontroller des Übergangs, und so müssen Sie eventuelle Initialisierungsschritte wie beispielsweise die Übergabe des Geburtsdatums auch hierbei in der Methode prepareForSegue:sender: durchführen.

Der Result-Viewcontroller kann nun das Geburtsdatum aus der Property auslesen und es weiterverarbeiten. In unserem Beispiel soll er das Alter des Nutzers ausgeben. Dazu legen Sie im Interface Builder ein Label in den Result-Viewcontroller und verbinden es über ein Outlet mit dem Namen ageLabel. Dieses Label soll der Viewcontroller immer aktualisieren, bevor de seinen View anzeigt. Aus diesem Grund überschreiben Sie die Methode viewWillAppear:, die das Alter des Nutzers über einen Kalender und Date-Components berechnet. Dabei berechnet die Methode components:fromDate:toDate:options: die Komponenten für die Zeitspanne zwischen dem Datum fromDate und dem Datum toDate.

- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
NSCalendar *theCalendar = [NSCalendar currentCalendar];
NSDateComponents *theComponents = [theCalendar
components:NSCalendarUnitYear | NSCalendarUnitMonth |
NSCalendarUnitDay fromDate:self.birthDate
toDate:[NSDate date] options:0];

self.ageLabel.text = [NSString stringWithFormat:
@"Sie sind %d Jahre, %d Monate und %d Tage alt.",
theComponents.year, theComponents.month,
theComponents.day];
}

Listing 3.52 Ausgabe des Alters in Jahren, Monaten und Tagen


Rheinwerk Computing - Zum Seitenanfang

3.2.10Ein kleines Intermezzo über LabelsZur nächsten ÜberschriftZur vorigen Überschrift

Mit dieser Methode gibt Ihnen die App zwar im Simulator die gewünschten Daten aus. Leider schneidet Cocoa Touch die Zeichenkette ab, da sie nicht komplett in die Zeile passt. Für zu lange Texte bietet die Klasse UILabel mehrere Strategien für die Anzeige an. Diese Strategien lassen sich teilweise kombinieren und über die Propertys lineBreakMode, numberOfLines und minimumScaleFactor oder im Attributinspektor des Interface Builders über die Felder Line Breaks, Lines oder Autoshrink einstellen.

Beschneiden des Textes

Eine Strategie haben Sie ja bereits kennengelernt: Wenn Sie nichts anderes einstellen, ersetzt das Label eine passende Anzahl von Buchstaben am Ende durch eine Ellipse (drei Auslassungspunkte). Sie können über den Line-Break-Mode jedoch auch andere Bereiche angeben, an denen das Label den angezeigten Text kürzt. [Anm.: Die Kürzung betrifft immer nur den angezeigten Text und niemals den Wert der Property text.] In Tabelle 3.2 finden Sie die Möglichkeiten, wie das Label den Text kürzen kann.

Tabelle 3.2 Unterschiedliche Line-Break-Modes für Textkürzungen

Modus Auswirkung

NSLineBreakByClipping

Das Label schneidet den Text genau an der rechten Kante ab. Das kann auch mitten in einem Buchstaben sein.

NSLineBreakByTruncatingHead

Dieser Modus ersetzt entsprechend viele Buchstaben am Anfang des Textes durch eine Ellipse.

NSLineBreakByTruncatingTail

Standardverhalten; wie im Text beschrieben

NSLineBreakByTruncatingMiddle

Das Label ersetzt entsprechend viele Buchstaben in der Mitte durch eine Ellipse.

Kleinere Zeichensätze

Wenn Sie im Attributinspektor des Labels unter Autoshrink den Eintrag Minimum Font Scale auswählen, verkleinert das Label den Zeichensatz so weit, dass der Text in das Label hineinpasst. Dabei geben Sie über den Wert in dem Eingabefeld den kleinsten zulässigen Skalierungsfaktor für den Zeichensatz an. Der Interface Builder schlägt hier den Wert 0,5 vor; also die halbe Größe. Falls der Text auch mit diesem kleinsten Zeichensatz nicht in das Label passt, schneidet ihn das Label entsprechend dem Line-Break-Mode ab.

Mehrzeilige Darstellung

Labels können die Texte auch in mehreren Zeilen anzeigen. Dazu geben Sie unter Lines beziehungsweise für numberOfLines einen Wert größer oder gleich als 0 für die Anzahl der erlaubten Zeilen an. Der Wert 0 bedeutet dabei, dass das Label so viele Zeilen verwenden darf, wie es für die Anzeige des kompletten Textes braucht. Über den Line-Break-Mode können Sie zusätzlich festlegen, wie das Label die Zeilen umbricht. Der Wert NSLineBreakByWordWrapping erlaubt den Zeilenumbruch nur an den Wortenden, während NSLineBreakByCharWrapping auch zwischen beliebigen Zeichen des Textes umbricht.

Das Beispielprojekt erlaubt dem Label, mehrere Zeilen anzuzeigen. Dazu müssen Sie zwei Änderungen am Label im Interface Builder vornehmen. Stellen Sie im Attributinspektor des Labels unter Lines den Wert 0 ein. Damit das Label jedoch auch genügend Platz für die Anzeige von mehreren Zeilen hat, sollten Sie zusätzlich seine Höhe ändern; die dreifache Höhe sollte hier ausreichen.


Rheinwerk Computing - Zum Seitenanfang

3.2.11Beliebige Objekte im StoryboardZur nächsten ÜberschriftZur vorigen Überschrift

Ein Storyboard enthält in der Regel alle Viewcontroller und deren Views einer App. Sie können daneben beliebige andere Objekte in einer Viewcontroller-Szene ablegen. Dazu verwenden Sie den orangenen Würfel (siehe Abbildung 3.44), den Sie in die Szene des Viewcontrollers ziehen, der das Objekt verwenden soll. Der Viewcontroller sollte das Objekt dabei über eine Outlet-Property mit dem Speicherverwaltungstyp strong halten, da es sich ja um ein Top-Level-Objekt handelt. Die Klasse des Objekts stellen Sie wie üblich über seinen Identitätsinspektor ein. Sie können so Objekte mit nahezu beliebigen Klassen im Storyboard anlegen.

Abbildung

Abbildung 3.44 Bibliothekssymbol für beliebige Objekte

Tipp

Wenn Sie einen einfachen Viewcontroller in einem Storyboard anlegen, sollten Sie unbedingt darauf achten, im Identitätsinspektor die Klasse des Viewcontrollers zu setzen. Andernfalls erzeugt Ihre App für den Viewcontroller ein Objekt mit der Klasse UIViewController, was die laufende App in der Regel mit einem Crash und der Meldung

- [UIViewController setXXX:]: unrecognized selector sent to 
instance 0xXXXXX
MyApp[15993:a0b] *** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[UIViewController
setXXX:]: unrecognized selector sent to instance 0xXXXXX'

quittiert. Diese Meldung entsteht bei dem Versuch, einer benutzerdefinierten Property einen Wert zuzuweisen. Da die Klasse UIViewController diese Property jedoch nicht kennt, gibt es diesen Fehler.

Auch bei beliebigen Objekten müssen Sie deren Klasse im Identitätsinspektor setzen, ansonsten legt Ihnen Cocoa Touch ein schnödes NSObject an. Übrigens können Sie in diesen Objekten auch Outlets und Action-Methoden anlegen, die Sie auch mit den Views des Viewcontrollers verknüpfen können.

Wenn Sie diese Objekte nach der Erzeugung initialisieren möchten, sollte ihre Klasse eine Initialisierungsmethode init oder initWithCoder: besitzen, wobei Cocoa Touch Letztere bevorzugt. Alternativ schreiben Sie den Initialisierungscode in die Methode awakeFromNib (siehe Abbildung 3.15); die Initialisierung läuft also analog zu den Views und den Viewcontrollern im Storyboard ab.


Rheinwerk Computing - Zum Seitenanfang

3.2.12Der Lebenszyklus eines ViewcontrollersZur nächsten ÜberschriftZur vorigen Überschrift

Sie haben bereits einige Methoden kennengelernt, die Cocoa Touch zu bestimmten Zeitpunkten im Leben eines Viewcontrollers aufruft. Wenn Sie beispielsweise die Methode viewDidAppear: überschreiben, können Sie eigenen Programmcode ausführen, unmittelbar nachdem Ihre App den View des Viewcontrollers angezeigt hat. Cocoa Touch benachrichtigt Sie also über ein bestimmtes Ereignis, das Ihren Viewcontroller betrifft. In Abbildung 3.45 ist der komplette Lebenszyklus eines Viewcontrollers anhand seiner Initialisierungs- und Benachrichtigungsmethoden sowie der Property view dargestellt. Bis auf die Property view dürfen Sie allerdings keine dieser Methoden direkt aufrufen. Sie dürfen sie nur überschreiben.

Abbildung

Abbildung 3.45 Der Lebenszyklus eines Viewcontrollers

Richtig überschreiben

Sie dürfen alle Methoden aus Abbildung 3.45 überschreiben. Dabei sollten Sie jedoch in der Methode – bis auf loadView und dealloc – auch immer die Methode in der Oberklasse aufrufen. Die Methode loadView sollten Sie allerdings nur überschreiben, wenn der Viewcontroller seinen View nicht aus dem Storyboard laden soll, sondern seine Views selbst erzeugt.

Bei initialisierenden Methoden wie awakeFromNib oder viewDidAppear: sollte der Aufruf immer am Anfang der Methode erfolgen. Bei den finalisierenden Methoden wie viewWillDisappear: oder viewDidUnload erfolgt der Aufruf der Supermethode hingegen erst am Schluss der überschreibenden Methode. Für die Methoden viewDidAppear: und viewWillDisappear: sieht das beispielsweise so aus:

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
// Hier steht Ihr Code
}
- (void)viewWillDisappear:(BOOL)inAnimated {
// Hier steht Ihr Code
[super viewWillDisappear:inAnimated];
}

Seit iOS 6 hat Apple die Methoden viewWillUnload und viewDidUnload als veraltet markiert. Seit dieser Version zerstört der Viewcontroller seinen View erst bei seiner eigenen Zerstörung. Sie dürfen die Methoden zwar weiterhin verwenden, allerdings kann Apple die Unterstützung dafür mit einer zukünftigen iOS-Version ganz einstellen. Abschnitt 3.2.13, »Speicher- und Ressourcenverwaltung des Viewcontrollers«, geht noch genauer darauf ein, wie Sie die Verwendung dieser Methoden umgehen können.

Das Überschreiben der Methode awakeFromNib ist in vielen Fällen sinnlos, da der Viewcontroller zum Zeitpunkt ihres Aufrufs den View noch nicht geladen und somit die Outlets noch nicht initialisiert hat. Der Viewcontroller lädt den View beim ersten Zugriff auf die Property view.

Die Standardimplementierung der Methode loadView lädt den View aus dem Storyboard. Sie können sie überschreiben, um den View durch Code zu erzeugen. Dazu weisen Sie in Ihrer Implementierung der Property view einfach Ihren erstellten View zu. In diesem Fall sollten Sie indes nicht die Methode in der Oberklasse aufrufen. Cocoa Touch ruft nach dem Laden des Views in loadView die Methode viewDidLoad auf, die Sie ja auch bereits kennengelernt haben.

Tipp

Wenn Sie Ihren Viewcontroller oder seinen View nach dem Laden initialisieren möchten, sollten Sie dazu die Methode viewDidLoad überschreiben. Wegen der fehlenden Outlet-Verbindungen ist ja die Methode awakeFromNib in den meisten Fällen nicht dafür geeignet. Die Methode loadView ist zum Laden des Views gedacht und sollte auch nur dafür überschrieben werden.

Sie können jetzt Ihren Viewcontroller beliebig oft anzeigen und wieder verschwinden lassen. In diesen Anzeigezyklen ruft Cocoa Touch viewWillAppear:, viewDidAppear:, viewWillDisappear: und viewDidDisappear: auf, die Sie ja bereits an einigen Stellen verwendet haben. Im nächsten Kapitel stellen wir mehrere Möglichkeiten vor, Viewcontroller anzuzeigen und wieder verschwinden zu lassen.


Rheinwerk Computing - Zum Seitenanfang

3.2.13Speicher- und Ressourcenverwaltung des ViewcontrollersZur vorigen Überschrift

Ein Viewcontroller verwaltet in der Regel mehrere Ressourcen. Das sind beispielsweise Views und andere Objekte, die er für seine Aufgabe benötigt und die seinen Zustand abbilden. Dabei besteht ein Unterschied zwischen wiederherstellbaren und nicht wiederherstellbaren Ressourcen. Zum Beispiel können Sie Texte oder Bilder wiederherstellen, die Sie aus dem Dateisystem geladen haben. Dazu führen Sie die Programmanweisungen wieder aus, mit der Sie diese Daten ursprünglich geladen haben. Das funktioniert natürlich nur, solange die App die Daten nicht über Nutzereingaben ändert, ohne sie zu speichern. In diesem Fall haben Sie ein Beispiel für nicht wiederherstellbare Daten.

Der Controller kann und sollte wiederherstellbare Ressourcen freigeben, sobald er sie nicht mehr benötigt. Dadurch vermindern Sie den Speicherverbrauch und erhöhen die Stabilität der App. Der Freigabezeitpunkt hängt dabei in der Regel vom Erzeugungszeitpunkt ab:

  1. Ressourcen, die der Controller in den Initialisierermethoden oder in awakeFromNib erzeugt, sollte er auch erst bei seiner Zerstörung wieder freigeben. Diese Ressourcen haben also den gleichen Lebenszyklus wie der Viewcontroller, und Sie sollten sie erst in der Methode dealloc freigeben.
  2. Sie können Ressourcen auch innerhalb des Viewzyklus verwalten, also beispielsweise in viewWillAppear: anfordern und in viewWillDisappear: wieder freigeben.

Diese beiden Fälle sind Faustregeln, von denen es aber durchaus Ausnahmen geben kann. Wie dem auch sei – Sie sollten die Ressourcen möglichst immer über Propertys verwalten, wie es die Beispielprojekte dieses Buches auch machen. Sie können die Ressourcen immer freigeben, indem Sie die entsprechende Property auf nil setzen. Für alle Outlets auf Nicht-Top-Level-Objekte – außer für die Arrays von Outlet-Collections – sollten Sie Propertys mit dem Speicherverwaltungstyp weak verwenden, da der Controller sie dadurch automatisch mit dem zugehörigen Top-Level-Objekt freigibt.

Top-Level-Objekte und die Arrays von Outlet-Collections müssen Sie hingegen immer halten, indem Sie den Speicherverwaltungstyp strong beziehungsweise retain verwenden. Außerdem müssen Sie für diese Objekte entscheiden, wann der Viewcontroller sie freigeben soll. Hierbei sind insbesondere die Ressourcen des Ladezyklus interessant. Vor der iOS-Version 6 hat Apple empfohlen, sie in der Methode viewDidUnload freizugeben. Diese Methode ist seit iOS 6 indes veraltet. Sie können sie die Objekte stattdessen in der Methode didReceiveMemoryWarning freigeben, wenn die Methode der Oberklasse den View des Controllers freigegeben hat. Der Code sollte also so aussehen:

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if(![self isViewLoaded]) {
// Freigabe aller wiederherstellbaren Ressourcen, die
// der Controller in viewDidLoad erzeugt oder lädt.
}
}

Listing 3.53 Ressourcenfreigabe unter iOS 6

Da es die Methode didReceiveMemoryWarning jedoch schon seit iOS 2 gibt, können Sie das Vorgehen aus Listing 3.35 auch schon für die älteren Betriebssystemversionen einsetzen. Dabei ist die Überprüfung allerdings wichtig. Wenn die App den View des Viewcontrollers während einer Speicherwarnung anzeigt, erhält der Controller zwar die Warnung, aber gibt dennoch nicht seinen View frei.

Tipp

Die Methode didReceiveMemoryWarning sollten Sie immer so implementieren, dass Cocoa Touch sie mehrmals aufrufen kann. Sie können die korrekte Funktionsweise übrigens gut im Simulator testen. Rufen Sie dazu einfach den Menüpunkt HardwareSpeicherwarnhinweis simulieren auf, dann sendet der Simulator eine Speicherwarnung an Ihre App.



Ihr Kommentar

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

>> Zum Feedback-Formular
<< zurück




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


Nutzungsbestimmungen | Datenschutz | Impressum

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


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






Neuauflage: 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: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo