4.3 Navigation- und Pop-over-Controller in der Praxis
So viel Controller-Theorie verlangt nach praktischen Beispielen. Dazu werden Sie den analogen Wecker um einen Einstellungsdialog erweitern. Dabei soll der Wecker drei Einstellungsmöglichkeiten erhalten:
- Der Nutzer soll den Alarmton an- und ausschalten können. Ein abgeschalteter Alarmton ist besonders für lärmempfindliche Personen und notorische Zuspätkommer praktisch. Letztere können sich dann zu Recht entschuldigen, sie hätten ihren Wecker nicht gehört.
- Über eine weitere Einstellung soll der Nutzer Ziffern auf dem Ziffernblatt einschalten können.
- Minimalisten schätzen die dritte Einstellungsmöglichkeit, mit der Sie die Unterteilung des Ziffernblattes verändern können.
Projektinformation
Den Quellcode des nachfolgenden Beispielprojekts finden Sie auf der DVD unter Code/Apps/Storyboard/AlarmClock oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/Storyboard/AlarmClock.
Für die zweite und dritte Einstellungsmöglichkeit müssen Sie die Klasse ClockView erweitern, damit sie diese Anzeigeoptionen auch darstellen kann. Dazu erhält die Klasse zunächst einmal einen Aufzählungstyp und zwei Propertys:
typedef enum {
PartitionOfDialNone = 0,
PartitionOfDialHours,
PartitionOfDialMinutes
} PartitionOfDial;
@interface ClockView : UIView
@property (nonatomic, strong) NSDate *time;
@property (nonatomic, strong, readonly) NSCalendar *calendar;
@property (nonatomic) BOOL showDigits;
@property (nonatomic) PartitionOfDial partitionOfDial;
- (void)startAnimation;
- (void)stopAnimation;
@end
Listing 4.19 Änderungen im Header von »ClockView.h«
Die Änderungen sehen Sie hervorgehoben in Listing 4.19. Der Aufzählungstyp PartitionOfDial benennt die drei möglichen Unterteilungen (keine, nur Stunden sowie Minuten und Stunden) für eine Uhr fest. Sie können einen Clockview über die Property partitionOfDial entsprechend konfigurieren. Mit der booleschen Property showDigits können Sie die Anzeige der Ziffern steuern. Das Zeichnen der Ziffern übernimmt die neue Methode drawDigits, die Sie vor der Methode drawRect: in die Implementierung der Klasse einfügen. Im Gegensatz zu den anderen Methoden verwendet sie ausschließlich Methoden aus dem UIKit und keine Core-Graphics-Funktionen zum Zeichnen. Das ist für die Darstellung von Text wesentlich einfacher, und Sie dürfen diese beiden Frameworks beim Zeichnen auch ruhig mischen.
- (void)drawDigits {
CGFloat theRadius = CGRectGetWidth(self.bounds) / 2.0;
UIFont *theFont = [UIFont systemFontOfSize:24];
CGRect theFrame;
[[UIColor darkGrayColor] set];
theRadius *= self.partitionOfDial == PartitionOfDialNone ?
0.9 : 0.6;
for(int i = 1; i <= 12; ++i) {
NSString *theText =
[NSString stringWithFormat:@"%d", i];
theFrame.origin = [self pointWithRadius:theRadius
angle:i * M_PI / 6];
theFrame.size = [theText sizeWithFont:theFont];
theFrame = CGRectOffset(theFrame,
-CGRectGetWidth(theFrame) / 2.0,
-CGRectGetHeight(theFrame) / 2.0);
[theText drawInRect:theFrame withFont:theFont];
}
}
Listing 4.20 Zeichnen des Ziffernblatts
Methoden zur Darstellung von Text stellt die Kategorie NSString(UIStringDrawing) zur Verfügung. Listing 4.20 verwendet zur Textdarstellung die Methode drawInRect:withFont:. Das Rechteck für die Darstellung berechnen Sie über die Größe des Textes, die Sie über die Methode sizeWithFont: ermitteln. Die bereits bekannte Methode pointWithRadius:angle: liefert den Mittelpunkt des darzustellenden Textes. Da Sie jedoch den Ursprungspunkt des Rechtecks benötigen, müssen Sie diesen durch eine Verschiebung um jeweils die halbe Breite und Höhe des Rechtecks berechnen. Die Verschiebung wird dabei von der Funktion CGRectOffset durchgeführt.
Der Wert der Schleifenvariablen i entspricht übrigens der darzustellenden Ziffer, und aus ihr lässt sich auch der Winkel
i·2
/12 = i·
/6 berechnen. Zur Erinnerung noch einmal die Herleitung dieser Formel. Eine Stunde ist
ein Zwölftel des Ziffernblattes. Ein Kreis hat 360°, also hat eine Stunde 360°/12 = 30°. Da das iPhone bekanntlich nur Winkel im Bogenmaß versteht, müssen Sie in dieser
Rechnung 360° durch 2
ersetzen und erhalten 2
/12 =
/6 als Winkel für eine Stunde.
Falls das Ziffernblatt keine Unterteilungsstriche anzeigt, verwendet die Methode einen größeren Radius, um die Ziffern weiter außen darzustellen. Außerdem stellt es die Ziffern in Dunkelgrau dar. Dazu setzt das Listing die Textzeichenfarbe über die Anweisung [[UIColor darkGrayColor] set].
Als Nächstes passen Sie die Methode drawRect: der Klasse ClockView entsprechend Listing 4.21 an. Hier sind nur die wesentlichen Zeilen für die Änderungen und jeweils drei Punkte für die restlichen Anweisungen angegeben, um die Übersichtlichkeit zu erleichtern. Für die Konfiguration der Unterteilung müssen Sie zwei Abfragen einfügen. Wenn das Ziffernblatt keine Unterteilung haben soll, braucht das Programm die Schleife nicht zu durchlaufen, und nur für die Darstellung von Minutenunterteilungen darf es den else-Block innerhalb der Schleife ausführen.
- (void)drawRect:(CGRect)inRectangle {
...
if(self.partitionOfDial != PartitionOfDialNone) {
// Zifferneinteilung zeichnen
for(NSInteger i = 0; i < 60; ++i) {
...
if(i % 5 == 0) {
...
}
else if(self.partitionOfDial == 
PartitionOfDialMinutes) {
// Minuteneinteilungsstrich darstellen
...
}
}
}
if(self.showDigits) {
[self drawDigits];
}
[self drawClockHands];
CGContextRestoreGState(theContext);
}
Listing 4.21 Anpassungen an der Methode »drawRect:«
Schließlich fügen Sie am Ende einen Aufruf der neuen Methode drawDigits in einem if-Block ein. Diese Methode darf das Programm schließlich nur ausführen, wenn es Ziffern darstellen soll.
Wenn Sie das Programm jetzt ausführen, sehen Sie einen weißen Kreis mit den Zeigern darauf. Da die Objective-C-Laufzeitumgebung alle Attribute einer Klasse mit 0 initialisiert, sind die Unterteilungsstriche und die Ziffern zunächst einmal ausgeschaltet. Sie sollten also nun Ihre App um einen Dialog erweitern, mit dem der Nutzer diese Einstellungen anpassen kann.
Erweiterung von universellen und lokalisierten Apps
Genauer gesagt müssen Sie vier Einstellungsdialoge erstellen, da Ihre App zwei Gerätefamilien und zwei Sprachen unterstützt. Das klingt nach viel Arbeit, die sich jedoch bei einer systematischen Vorgehensweise relativ gut begrenzen lässt. Erweitern Sie die App zunächst nur für eine Gerätefamilie (am besten für das iPhone) und eine Sprache.
Achten Sie dabei jedoch darauf, dass Ihre Testumgebung (Simulator, iOS-Geräte) auch genau zu dieser Konfiguration passt. Das gilt insbesondere für die Spracheinstellungen. Erst wenn Sie die Anpassungen für diese Konfiguration beendet haben, nehmen Sie die anderen Sprachen und die andere Gerätefamilie (also das iPad) in Angriff – allerdings auch hier immer nur jeweils eine Konfiguration gleichzeitig. Dabei können Sie neue Viewcontroller einfach in die anderen Storyboards kopieren. Das geht sogar mit mehren Viewcontrollern auf einmal, wobei Xcode dann sogar die Übergänge zwischen diesen Controllern mitkopiert.
4.3.1 Navigationcontroller anlegen

Die Wecker-App braucht also einen Einstellungsdialog, den sie über einen Navigationcontroller
anzeigt. Dazu erweitern Sie zunächst das englische Storyboard für iPhones. Da die
App den Einstellungsdialog über einen Navigationcontroller anzeigt, ziehen Sie zunächst
aus der Bibliothek einen solchen Controller auf die Zeichenfläche des Storyboards.
Der Interface Builder legt dabei automatisch einen neuen Rootviewcontroller an, den
Sie jedoch nicht brauchen. Selektieren Sie dieses Objekt, und löschen Sie es über
die Taste
oder
. Als Nächstes ziehen Sie eine Verbindung vom Navigation- zum vorhandenen Viewcontroller
des Weckers und wählen im darauf erscheinenden Pop-over-Dialog den Punkt root view controller aus (siehe Abbildung 4.19). Sie haben dem Navigationcontroller nun einen neuen Rootviewcontroller zugewiesen.
Der Alarmclock-Viewcontroller sollte jetzt auch eine Navigationsleiste anzeigen. Dort löschen Sie das Label Alarm Clock und tragen stattdessen diesen Text in den Titel des Navigation-Items ein. Durch einen Doppelklick in die Mitte der Leiste können Sie diese Änderungen direkt in der simulierten Navigationsleiste vornehmen. Außerdem legen Sie dort auf der rechten Seite einen Button (Typ UIBarButtonSystemItemCompose) für den Aufruf der Einstellungen an. Durch die Anzeige der Navigationsleiste verschieben sich die Views der Zeitanzeige. Kontrollieren und korrigieren Sie die Abmessungen des Ziffernblatts, sodass es wieder quadratisch ist.
Die App sollte nun den Navigationcontroller als initialen Viewcontroller des Fensters verwenden. Dazu öffnen Sie den Attributinspektor des Navigationcontrollers und setzen dort das Häkchen bei Is Initial View Controller. Beim Viewcontroller für die Zeitanzeige entfernen Sie dieses Häkchen. Nach diesen Änderungen sollte Ihr Storyboard so wie in Abbildung 4.28 aussehen.
Abbildung 4.28 Der Navigationcontroller in der Wecker-App
Um Ihre Änderungen zu überprüfen, sollten Sie die App starten. Achten Sie dabei darauf, dass die Spracheinstellungen des Simulators beziehungsweise des Geräts der Sprache des Storyboards entsprechen. Die App sollte nun eine Navigationsleiste mit dem Titel und einem Button anzeigen.
4.3.2 Einen Dialog für die Einstellungen gestalten

Als Nächstes erstellen Sie den Dialog für die Einstellungen der App. Dazu eignen sich hervorragend statische Tableviews, die Apple mit iOS 5 eingeführt hat. Ein Tableview verwaltet seinen Inhalt in untereinanderliegenden Zellen, die er in Abschnitte gruppieren kann. Während die Anzahl der Zellen in einem dynamischen Tableview von der darzustellenden Datenmenge der Applikation abhängt, ist sie bei statischen Ta-bleviews fest. Dynamische Tableviews lernen Sie in Kapitel 5, »Daten, Tabellen und Controller«, kennen.
Um statische Tableviews nutzen zu können, müssen Sie einen Tableviewcontroller verwenden. Ziehen Sie ein solches Objekt aus der Bibliothek auf die Zeichenfläche Ihres Storyboards. Der Tableviewcontroller enthält bereits einen Tableview, den Sie allerdings noch konfigurieren müssen, wozu Sie dessen Attributinspektor öffnen (siehe Abbildung 4.29). Wählen Sie unter Content den Punkt Static Cells aus, um einen statischen Tableview zu erhalten. Der Tableview soll sich in zwei Abschnitte aufteilen, weswegen Sie unter Sections den Wert 2 einstellen. Wenn Sie unter Style den Eintrag Grouped auswählen, zeichnet der Tableview einen grauen Rahmen um die Abschnitte und rundet deren Ecken ab. Außerdem sollten Sie die Auswahl von Zellen unterbinden, indem Sie unter Selection den Punkt No Selection auswählen.
Als Nächstes können Sie die Zellen des Tableviews gestalten. Im ersten Abschnitt benötigen Sie zwei Zellen und im zweiten Abschnitt eine Zelle. Der Interface Builder hat in jedem Abschnitt jeweils drei Zellen angelegt. Sie brauchen also nur die überflüssigen Zellen zu löschen. In alle Zellen ziehen Sie jeweils ein Label, deren Texte Sie in »Digits« (Ziffern), »Partition of Dial« (Ziffernblatteinteilung) und »Sound« (Ton) umändern. In die Zellen für das Ein- und Ausschalten der Ziffern und des Tons fügen Sie jeweils auf der rechten Seite noch einen Schalter (UISwitch) ein.
Abbildung 4.29 Einstellungen für den statischen Tableview
Für die Einstellung der Ziffernblatteinteilung brauchen Sie ein Segmented Control. Das ist ein Schalter, der mehrere Zustände haben kann. Für die App benötigen Sie drei Zustände (keine Einteilung, Stundeneinteilung sowie Stunden- und Minuteneinteilung). Allerdings reicht dafür der Platz in der Zelle nicht aus. Sie können jedoch die Höhe der Zelle in deren Größeninspektor verändern. Tragen Sie unter Row Height den Wert 92 ein, dann lässt sich das Control unter dem Label platzieren.
Abbildung 4.30 Der Einstellungsdialog des Weckers
Die Absatzüberschriften können Sie anpassen, indem Sie die Objekte mit der Bezeichnung Table View Section in der Baumdarstellung auswählen. Öffnen Sie deren Attributinspektor, und geben Sie unter Header jeweils »Appearance« und »Alarm« ein. Der fertige View sollte so wie in Abbildung 4.30 aussehen.
Als Nächstes legen Sie den Übergang zum Einstellungsdialog an. Dazu ziehen Sie eine Verbindung vom Button in der Navigationsleiste des Viewcontrollers für die Zeitanzeige zu dem neuen Controller für die Einstellungen. In der Auswahl (siehe Abbildung 4.19) selektieren Sie den Punkt push. Sie können nun den Einstellungsdialog in Ihrer App aufrufen, indem Sie auf diesen Button tippen. Da Sie bei seinem Vorgänger einen Titel in das Navigations-Item eingetragen haben, zeigt die Navigationsleiste beim Einstellungsdialog auch einen Zurückbutton an, über den Sie zur Zeitanzeige zurückkehren können.
4.3.3 Einstellungen dauerhaft speichern

Die App zeigt den Einstellungsdialog zwar an und auch die Schalter funktionieren bereits, das hat allerdings noch keinen Einfluss auf die Zeitanzeige. Der Einstellungsdialog benötigt also eine eigene Logik in Form einer eigenen Klasse. Dafür legen Sie im Projekt eine Unterklasse von UITableViewController mit dem Namen PreferencesViewController an, die Sie dem Viewcontroller im Storyboard über dessen Identitätsinspektor zuweisen. Anschließend löschen Sie aus der Implementierungsdatei den kompletten Inhalt des Implementierungsblocks.
In die neue Klasse fügen Sie für jedes der drei Controls eine passende Outlet-Property mit dem Speicherverwaltungstyp weak hinzu, sodass die Klassendeklaration wie in Listing 4.22 aussieht. Am einfachsten legen Sie diese drei Propertys an, indem Sie jeweils eine Verbindung von den Controls in die Headerdatei des Einstellungscontrollers ziehen und die entsprechenden Namen eingeben.
@interface PreferencesViewController : UITableViewController
@property (weak, nonatomic) IBOutlet UISwitch *digitsSwitch;
@property (weak, nonatomic) IBOutlet
UISegmentedControl *partitionControl;
@property (weak, nonatomic) IBOutlet UISwitch *soundSwitch;
@end
Listing 4.22 Klassendeklaration für den Einstellungsdialog
Xcode fügt bis zur Version 4.4 noch Synthesize-Anweisungen in die Implementierung ein, die die Version 4.0 des LLVM-Compilers von Xcode 4.5 nicht mehr benötigt. Außerdem legt Xcode noch die Methode viewDidUnload an, die die Outlets freigibt. Da Sie dafür jedoch den Speicherverwaltungstyp weak verwendet haben, ist das nicht notwendig, und Sie können die komplette Methode löschen.
Natürlich haben Sie durch die Einführung dieser neuen Klasse das Verhalten des Programms noch nicht verändert. Es stellt sich nun die Frage, wie der Einstellungsdialog die Änderungen an den Wecker übergeben soll. Die naive Lösung zu dieser Frage lautet: Der Controller für die Einstellungen besitzt eine Referenz auf den Clockview oder den Viewcontroller für die Zeitanzeige und schreibt die geänderten Werte direkt oder indirekt in den Clockview. Bei dieser Lösung entsteht meist eine starke Abhängigkeit zwischen den Viewcontroller- und/oder Viewklassen.
Um unsere Frage zu beantworten, sollten Sie noch eine weitere, implizite Anforderung berücksichtigen: Die Einstellungswerte sollen über einen Neustart der App erhalten bleiben. Sie müssen also die Einstellungswerte nicht nur an das Ziffernblatt, sondern auch an eine Komponente übertragen, die diese Werte dauerhaft speichern kann. Mit der Lösung dieser Anforderung erhalten Sie auch eine recht elegante Lösung für das oben beschriebene Abhängigkeitsproblem.
Die Klasse NSUserDefaults aus dem Foundation-Framework erlaubt eine permanente Speicherung nahezu beliebiger Schlüssel-Wert-Paare. Dazu verwenden Sie in der Regel das Objekt, das Ihnen die Klassenmethode standardUserDefaults liefert. Listing 4.23 enthält zwei Methoden, mit denen der Einstellungsdialog seine Werte speichern und wiederherstellen kann.
Wofür »NSUserDefaults« verwenden?
Sie können zwar in den Userdefaults beliebig große Daten und Datenmengen abspeichern, diese Klasse ist jedoch vornehmlich für die Einstellungsdaten der App und nicht für die Ablage größerer Dokumente oder Daten gedacht. Wenn Sie größere Datenmengen dauerhaft speichern möchten, sollten Sie andere Möglichkeiten, wie das Dateisystem oder das Framework Core Data, nutzen. Das Fototagebuch in Kapitel 5 zeigt ein Beispiel für die Nutzung von Core Data.
Fügen Sie diese beiden Methoden zu der Implementierung der Klasse PreferencesViewController hinzu, und deklarieren Sie sie auch im Header. Das Userdefaults-Objekt benutzen Sie dabei wie ein Verzeichnis. Sie können jeweils unter einem Schlüssel einen Wert dauerhaft speichern, und diesen Wert können Sie anhand des Schlüssels wiederherstellen. Beispielsweise speichert die Wecker-App die Ziffernblatteinteilung als einen Ganzzahlwert unter dem Schlüssel partitionOfDial ab.
- (void)savePreferences {
NSUserDefaults *theDefaults = 
[NSUserDefaults standardUserDefaults];
[theDefaults setBool:self.digitsSwitch.on 
forKey:@"showDigits"];
[theDefaults 
setInteger:self.partitionControl.selectedSegmentIndex 
forKey:@"partitionOfDial"];
[theDefaults setBool:self.soundSwitch.on 
forKey:@"playSound"];
[theDefaults synchronize];
}
- (void)restorePreferences {
NSUserDefaults *theDefaults = 
[NSUserDefaults standardUserDefaults];
[theDefaults synchronize];
self.digitsSwitch.on = 
[theDefaults boolForKey:@"showDigits"];
self.partitionControl.selectedSegmentIndex = 
[theDefaults integerForKey:@"partitionOfDial"];
self.soundSwitch.on = 
[theDefaults boolForKey:@"playSound"];
}
Listing 4.23 Wiederherstellung und Speicherung der Einstellungen
Einstellungen synchronisieren
NSUserDefaults legt die Einstellungswerte nicht sofort im Dateisystem des iOS-Gerätes ab, sondern nur in periodischen Zeitabständen. Durch einen Aufruf der Methode synchronize können Sie allerdings die sofortige Speicherung erzwingen. Diese Methode dient auch dazu, die aktuellen Einstellungen aus dem Dateisystem zu laden.
Sie können nun diese beiden Methoden aufrufen, bevor beziehungsweise nachdem die App den Einstellungsdialog anzeigt. Dazu überschreiben Sie die Methoden viewWillAppear: und viewWillDisappear: so wie in Listing 4.24:
- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
[self restorePreferences];
}
- (void)viewWillDisappear:(BOOL)inAnimated {
[self savePreferences];
[super viewWillDisappear:inAnimated];
}
Listing 4.24 Wiederherstellung und Speicherung des Anzeigezustands
Der Einstellungsdialog behält nun seine Werte auch über einen Programmneustart hinweg bei. Das sollten Sie ruhig einmal mit ein paar Testläufen ausprobieren.
Jetzt fehlt nur noch die Übergabe der Werte an die Zeitanzeige. Dafür können Sie das gleiche Vorgehen wie beim Einstellungsdialog verwenden. Das heißt, der Viewcontroller für die Zeitanzeige liest die Werte aus den Userdefaults und übergibt sie an den View. Diese Aufgabe übernimmt die Methode updateClockView, die Sie in die Implementierung der Klasse AlarmClockViewController einfügen und von der Methode viewWillAppear: aus aufrufen (siehe Listing 4.25). Da Cocoa Touch beim Entfernen des Einstellungsdialogs aus dem Navigationcontroller diese Methode aufruft, übernimmt die Uhr dadurch auch die neuen Einstellungswerte in die Anzeige, und es entsteht auch keine Abhängigkeit zwischen den beiden Viewcontrollern.
- (void)updateClockView {
NSUserDefaults *theDefaults = 
[NSUserDefaults standardUserDefaults];
self.clockView.showDigits = 
[theDefaults boolForKey:@"showDigits"];
self.clockView.partitionOfDial = 
[theDefaults integerForKey:@"partitionOfDial"];
[self.clockView setNeedsDisplay];
}
- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
[self updateClockView];
[self updateControl];
}
Listing 4.25 Aktualisierung der Einstellungen in der Zeitanzeige
Nun können Sie über den Einstellungsdialog das Aussehen der Zeitanzeige verändern, und die App speichert Ihre Einstellungen auch dauerhaft ab. Dabei nutzt die App zur Lösung der Frage nach der Wertübergabe die Userdefaults, was den angenehmen Nebeneffekt hat, dass die App die Einstellungen auch dauerhaft speichert.
Etwas begriffsstutzig ...
... scheint der Clockview zu sein, wenn Sie den Aufruf der Methode setNeedsDisplay in updateClockView weglassen. Dann sieht er nämlich nach dem Verlassen des Einstellungsdialogs zunächst genau so aus wie vorher. Nach einer kurzen Zeit springt dann die Anzeige auf die neuen Werte um. Das Ziffernblatt zeigt die neuen Einstellungen nämlich erst an, wenn der Timer die Zeitanzeige aktualisiert. Durch den Aufruf von setNeedsDisplay erzwingen Sie die Aktualisierung jedoch schon vorher.
Die App wertet jetzt die Einstellungen für die Ziffernblatteinteilung und die Anzeige der Ziffern aus. Über die dritte Einstellung können Sie den Ton für die lokale Benachrichtigung an- oder abschalten. Um diese Einstellungsmöglichkeit zu aktivieren, müssen Sie die Methode createAlarm in AlarmClockViewController ändern und dort die Zuweisung der Property soundName in eine if-Abfrage packen:
if([[NSUserDefaults standardUserDefaults]
boolForKey:@"playSound"]) {
theNotification.soundName = @"ringtone.caf";
}
Listing 4.26 Anpassung, um den Sound abzuschalten
Wenn die App zur Laufzeit eine lokale Benachrichtigung empfängt, spielt sie den Ton ab. Auch hier sollte sie die Einstellungen berücksichtigen. Dazu müssen Sie die Methode playSound im App-Delegate anpassen:
- (void)playSound {
if([[NSUserDefaults standardUserDefaults]
boolForKey:@"playSound"]) {
NSNumber *theId = self.soundId;
if(theId) {
AudioServicesPlaySystemSound(
[theId unsignedIntValue]);
}
}
}
Listing 4.27 Den Ton nur bei der entsprechenden Einstellung abspielen
Wenn Sie die Applikation jetzt starten, zeigt sie ein leeres Ziffernblatt an. Das liegt daran, dass auch die Einstellungen noch leer sind. Sie können sie zwar jetzt leicht über den Dialog ändern, es ist allerdings schöner, wenn die App beim ersten Start das Ziffernblatt mit Unterteilungen anzeigt. Über die Methode registerDefaults: können Sie in den Userdefaults Standardwerte anlegen, ohne Sie jedoch dort zu speichern. Das Userdefaults-Objekt verwendet diese Werte, wenn es zu einem Schlüssel keinen Wert enthält. Um Standardwerte zu setzen, fügen Sie die Anweisungen aus Listing 4.28 in die Methode application:didFinishLaunchingWithOptions: ein; danach sollte die App auch beim ersten Start Ziffern und Unterteilungen anzeigen, die sich über den Einstellungsdialog aber dauerhaft ändern lassen.
NSUserDefaults *theDefaults =
[NSUserDefaults standardUserDefaults];
[theDefaults registerDefaults:@{
@"showDigits" : @YES,
@"partitionOfDial" : @(PartitionOfDialMinutes),
@"playSound" : @YES
}];
Listing 4.28 Anlegen von Standardeinstellungen
Das erste Mal
Sie können den ersten Start einer App im Simulator und auf dem Gerät beliebig oft simulieren, indem Sie einfach die App über das Springboard löschen. Halten Sie dazu das App-Icon so lange gedrückt, bis die Icons zu wackeln anfangen. Sie können die App nun über das Kreuz an der linken oberen Ecke des Icons löschen.
Mit diesen Änderungen haben Sie die Umstellung der englischen Version des Programms für das iPhone abgeschlossen. Als Nächstes sollten Sie sich die deutsche Version für das iPhone vornehmen. Da Sie nur geringfügige Anpassungen am View der Zeitanzeige im Storyboard vorgenommen haben, sollten Sie einfach den Viewcontroller aus dem deutschen Storyboard löschen und alle Viewcontroller aus dem englischen Storyboard ins deutsche kopieren. Sie können auch einfach das deutsche Storyboard über dessen Dateiinspektor löschen und danach eine neue deutsche Lokalisierung aus der englischen anlegen. Danach brauchen Sie nur noch die Texte zu übersetzen. Änderungen am Programmcode oder anderen Dateien sind nicht notwendig.
4.3.4 Anpassung an das iPad
Die Anpassungen für das iPad können Sie nun analog zu der Erstellung des deutschen Storyboards durchführen, indem Sie die Elemente aus dem englischen Storyboard für das iPhone in ein neues Storyboard für das iPad kopieren. Achten Sie dabei darauf, den initialen Viewcontroller richtig zu setzen und in den Targeteinstellungen das Main Storyboard für das iPad auszuwählen. Wenn Sie den Einstellungsdialog der iPad-App danach aufrufen, stellen Sie allerdings fest, dass er optisch nicht sehr ansprechend ist. Das iPad zieht den Tableview mit den Einstellungen auf die volle Bildschirmgröße auf, und der Wechsel zwischen den beiden Viewcontrollern sieht sehr unruhig aus, weil Cocoa Touch den kompletten Bildschirmbereich auswechselt. Führen Sie diese Umstellung für die englische Version jedoch trotzdem aus, weil sie die Basis für eine iPad-spezifische Anpassung ist.
Auf dem iPad ist ein Pop-over für die Anzeige des Einstellungsdialogs geeigneter, da Sie hier die Anzeigegröße des Views frei wählen können. Dazu müssen Sie im Interface Builder erst mal diesen View kleinkriegen. Dazu wählen Sie im Attributinspektor des Einstellungsdialogs unter Size den Punkt Freeform. Durch diese Einstellung können Sie jetzt die Größe des Tableviews in dessen Größeninspektor verändern. Stellen Sie hier eine Breite und Höhe von jeweils 320 Pixeln ein.
Als Nächstes ändern Sie den Übergang von der Zeitanzeige zu den Einstellungen von Push auf Pop-over um. Selektieren Sie dazu den Übergang im Storyboard, öffnen Sie dessen Attributinspektor, und wählen Sie dort Popover unter Style aus. Nach dieser Änderung öffnet die App die Einstellungen in einem Pop-over.
Abbildung 4.31 Der Einstellungsdialog als Pop-over
Bei den Anpassungen für das iPad brauchten Sie bis jetzt keine Zeile Code zu schreiben. Die App wählt beim Programmstart das passende Storyboard zur Gerätefamilie aus, und das Storyboard legt dann über den Übergang die Anzeigevariante des Einstellungsdialogs fest.
Allerdings funktioniert die App auf dem iPad noch nicht ganz zufriedenstellend. Wenn Sie den Einstellungsdialog schließen, übernimmt die Zeitanzeige die neuen Werte nicht. Sie müssen dafür die App erst neu starten. Das liegt daran, dass Cocoa Touch die Methode viewWillAppear: beim Schließen des Pop-ups nicht aufruft. Die App zeigt ja das Ziffernblatt auch während der Darstellung des Einstellungsdialogs an, und so ist dieser Methodenaufruf hier nicht sinnvoll. Die Zeitanzeige muss also auf einem anderen Weg die Änderung der Einstellungen mitbekommen.
Dazu eignet sich das Key-Value-Observing, das Sie bereits in Kapitel 2, »Grundlagen«, kennengelernt haben. Der Viewcontroller für die Zeitanzeige registriert sich dazu in der Methode viewDidAppear: als Beobachter für die Schlüssel showDigits und partitionOfDial der Userdefaults. Entsprechend dazu muss der Viewcontroller die Beobachtung in viewWillDisappear: auch wieder aufheben. Dies ist ein typischer Fall für die in Kapitel 3, »Views und Viewcontroller«, genannte Faustregel für die Ressourcenverwaltung.
Die Beobachtermethode muss nun lediglich die Methode updateClockView aufrufen. Den Programmcode dafür finden Sie in Listing 4.29. Nach dieser Änderung übernimmt das Ziffernblatt die Einstellungen, sobald Sie den Einstellungsdialog schließen.
- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
NSUserDefaults *theDefaults = 
[NSUserDefaults standardUserDefaults];
[theDefaults addObserver:self forKeyPath:@"showDigits" 
options:0 context:nil];
[theDefaults addObserver:self 
forKeyPath:@"partitionOfDial" options:0 context:nil];
[self.clockView startAnimation];
}
- (void)viewWillDisappear:(BOOL)inAnimated {
NSUserDefaults *theDefaults = 
[NSUserDefaults standardUserDefaults];
[theDefaults removeObserver:self 
forKeyPath:@"showDigits"];
[theDefaults removeObserver:self 
forKeyPath:@"partitionOfDial"];
[self.clockView stopAnimation];
[super viewWillDisappear:inAnimated];
}
- (void)observeValueForKeyPath:(NSString *)inKeyPath
ofObject:(id)inObject change:(NSDictionary *)inChange
context:(void *)inContext {
[self updateClockView];
}
Listing 4.29 An- und Abmeldung für das Key-Value-Observing
Für den Nutzer wäre es natürlich hilfreich, wenn er die Auswirkungen der Einstellungsänderungen unmittelbar sehen könnte – wenn also das Ziffernblatt die Einstellungen schon aus dem geöffneten Dialog übernehmen würde. Auch diese Anforderung lässt sich leicht erfüllen. Dazu muss die App lediglich die Einstellungen immer dann in den Userdefaults speichern, wenn der Nutzer eine Einstellung ändert. Durch das Key-Value-Observing leitet der Viewcontroller für die Zeitanzeige die neuen Werte unmittelbar an den Clockview weiter.
Sie können dieses Verhalten erreichen, indem Sie in der Klasse PreferencesViewController eine Actionmethode dafür bereitstellen, die Sie mit den Value-Changed-Events der Controls verbinden. Am einfachsten verwenden Sie dafür die Methode savePreferences, indem Sie deren Ergebnistyp in IBAction ändern. Wenn Sie nun die App auf einem iPad starten und den Einstellungsdialog aufrufen, übernimmt das Ziffernblatt die neuen Einstellungen, sobald Sie einen Wert im geöffneten Pop-up-Dialog ändern.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.








Jetzt bestellen





