3.3 Lokale Benachrichtigungen
Dem Wecker fehlt jetzt nur noch ein Alarmton, der zur gewählten Zeit erklingt. Sie können natürlich die Zeitdifferenz zum aktuellen Zeitpunkt berechnen und einen Timer starten. Dieser ruft nach Ablauf der Zeit eine Methode auf, die einen Ton abspielt. Das funktioniert allerdings nur, wenn Ihr Programm die ganze Zeit im Vordergrund weiterläuft. Sie können also Ihr iPhone in der Zwischenzeit nur eingeschränkt nutzen.
Das Beispielprogramm verwendet stattdessen lokale Benachrichtigungen, die Apple mit iOS 4.0 eingeführt hat. Das sind zeitgesteuerte Ereignisse, die das Betriebssystem zu einem bestimmten Zeitpunkt auslöst. Eine lokale Benachrichtigung kann beim Auslösen eine Nachricht auf dem Bildschirm anzeigen, eine Audiodatei abspielen oder den Wert im Badge der App auf einen festen Wert setzen. Die Badges sind die roten Plaketten am App-Icon, die viele Apps (z. B. Mail) verwenden (siehe Abbildung 3.36). Lokale Benachrichtigungen funktionieren auch, wenn Sie Ihre App in den Hintergrund schicken oder sogar stoppen.
Vor iOS 5 hat Cocoa Touch die Nachrichten in einer Alertbox angezeigt (siehe Abbildung 3.37), und seit iOS 5 erfolgt die Anzeige über die Mitteilungszentrale. Sie stellt lokale Benachrichtigungen standardmäßig als Banner am oberen Bildschirmrand dar. Sie können in der App Einstellungen unter Mitteilungen • AlarmClock [Nach der Lokalisierung der App in Abschnitt Sprachkursus für die App heißt dieser Punkt Wecker.] • Hinweisstil aber Hinweise auswählen, um die Nachrichten in Alertboxen anzeigen zu lassen. Eine (Vor-)Auswahl dieses Anzeigestils über die App ist dagegen leider nicht möglich.
Abbildung 3.36 App-Icons mit Badges
Nachrichten an aktive Apps
Die Nachricht erscheint dagegen nicht, wenn die App im Vordergrund läuft. Stattdessen ruft Cocoa Touch eine Methode des Applicationd-Delegates auf. In diesem Fall können Sie also selbst entscheiden, was Sie mit der Nachricht anfangen wollen.
Abbildung 3.37 Anzeige einer lokalen Benachrichtigung in einer Alertbox ...
Abbildung 3.38 ... und in einem Banner
Sie definieren eine neue Benachrichtigung, indem Sie ein Objekt der Klasse UILocalNotification erzeugen und den Auslösezeitpunkt setzen. Außerdem können Sie einen Nachrichtentitel und -text angeben, wenn Sie eine Alertbox anzeigen lassen wollen. Für den Benachrichtigungston und das Badge können Sie den Namen des Tons beziehungsweise den Wert übergeben. Die neue Benachrichtigung fügen Sie danach in die Warteschlange für lokale Benachrichtigungen ein, auf die Sie über Methoden des Application-Singletons zugreifen können. Cocoa Touch liest die Benachrichtigungen von dort, löst sie zum gewünschten Zeitpunkt aus und löscht sie danach, falls es sich um eine einmalige Benachrichtigung handelt.
3.3.1 Benachrichtigungen versenden

Die Methode scheduleLocalNotification: der Klasse UIApplication fügt eine Benachrichtigung in die Warteschlange ein, und cancelLocalNotification: entfernt sie wieder daraus; die Methode cancelAllLocalNotifications entfernt alle Benachrichtigungen der App aus der Warteschlange. Über die Property scheduledLocalNotifications können Sie alle Benachrichtigungen aus der Warteschlange auslesen, und ab iOS 4.2 können Sie sie auch setzen. Sie können in der Warteschlange allerdings nur auf die Benachrichtigungen Ihrer App zugreifen. Der Zugriff auf die Benachrichtigungen anderer Apps ist nicht möglich. Ebenso wenig können Sie anderen Apps über diesen Mechanismus eine Benachrichtigung schicken.
Die Methoden aus Abschnitt 3.2.8, »Direkte Gestenverarbeitung«, haben die Methode updateAlarm aufgerufen, um das Auslösen des Alarms zu aktualisieren. Diese Methode löscht entweder alle Benachrichtigungen der App, wenn Sie den Alarm ausgeschaltet haben, oder legt über einen Aufruf der Methode createAlarm eine neue Benachrichtigung an.
- (IBAction)updateAlarm {
self.alarmHidden = !self.alarmSwitch.on;
if(self.alarmSwitch.on) {
[self createAlarm];
}
else {
UIApplication *theApplication =
[UIApplication sharedApplication];
[theApplication cancelAllLocalNotifications];
}
}
Listing 3.42 Aktualisierung des Alarms
Die Methode createAlarm in der Klasse AlarmClockViewController erzeugt eine neue Benachrichtigung:
const NSTimeInterval kSecondsOfDay = 60.0 * 60.0 * 24.0;
- (void)createAlarm {
UIApplication *theApplication =
[UIApplication sharedApplication];
UILocalNotification *theNotification =
[[UILocalNotification alloc] init];
NSTimeInterval theTime = self.startTimeOfCurrentDay +
clockControl.time;
while(theTime < [NSDate timeIntervalSinceReferenceDate]) {
theTime += kSecondsOfDay / 2.0;
}
[theApplication cancelAllLocalNotifications];
theNotification.fireDate =
[NSDate dateWithTimeIntervalSinceReferenceDate:theTime];
theNotification.timeZone = [NSTimeZone defaultTimeZone];
theNotification.alertBody = @"Aufwachen";
theNotification.soundName =
UILocalNotificationDefaultSoundName;
[theApplication scheduleLocalNotification:theNotification];
[theNotification release];
}
Listing 3.43 Erzeugung einer lokalen Benachrichtigung
Zuerst muss die Methode die Alarmzeit in ein NSDate-Objekt umwandeln. Die Alarmzeit liegt als relative Zeit zum Beginn des aktuellen Tages oder der letzten Mittagszeit vor, da das Ziffernblatt nur 12 und nicht 24 Stunden umfasst. Die Berechnung erfolgt nun so, dass zu der Startzeit des Tages die Alarmzeit hinzugerechnet wird. Solange diese Zeit noch kleiner als die aktuelle Zeit ist, zählt die Methode jeweils die Sekunden eines halben Tages hinzu. Dabei enthält die Konstante kSecondsOfDay die Anzahl der Sekunden eines Tages als Zeitintervall. Bevor Sie jedoch die neue Alarmzeit setzen, müssen Sie die eventuell noch vorhandenen Benachrichtigungen aus der Warteschlange löschen. Als Alarmton verwenden Sie den Systemton für Benachrichtigungen, indem Sie der Property soundName die Konstante UILocalNotificationDefaultSoundName zuweisen.
Die Methode startTimeOfCurrentDay berechnet den Startzeitpunkt des Tages zu der aktuellen Uhrzeit. Dazu verwenden Sie einen Kalender, mit dem Sie das aktuelle Datum ohne Uhrzeit in einem NSDateComponents-Objekt berechnen lassen. Da alle Zeitkomponenten auf 0 stehen, hat dieses Objekt die Uhrzeit 0:00 Uhr. Sie brauchen es also nur über den Kalender wieder in ein NSDate-Objekt zurückrechnen lassen:
- (NSTimeInterval)startTimeOfCurrentDay {
NSCalendar *theCalendar = [NSCalendar currentCalendar];
NSDateComponents *theComponents = 
[theCalendar components:NSYearCalendarUnit |
NSMonthCalendarUnit |
NSDayCalendarUnit
fromDate:[NSDate date]];
NSDate *theDate =
[theCalendar dateFromComponents:theComponents];
return [theDate timeIntervalSinceReferenceDate];
}
Listing 3.44 Berechnung des Startzeitpunktes des aktuellen Tages
Damit haben Sie den Versand einer lokalen Benachrichtigung abgeschlossen. Denken Sie beim Ausprobieren der App aber daran, dass sie die Benachrichtigungen nicht anzeigt, wenn die App im Vordergrund läuft. Sie sollten die App also vor der Versendung über den Homebutton des Simulators in den Hintergrund schicken oder sie über Xcode stoppen.
Die App soll nach einem Neustart natürlich auch wieder die aktuelle Alarmzeit anzeigen. Dazu muss sie vor der Anzeige des Views die letzte Benachrichtigung auslesen und deren Zeit auf das Clockcontrol und das Label für die Anzeige der Alarmzeit übertragen, was die Methode updateViews übernimmt.
- (void)updateViews {
UIApplication *theApplication = 
[UIApplication sharedApplication];
UILocalNotification *theNotification = [[theApplication
scheduledLocalNotifications] lastObject];
if([theNotification.fireDate compare:[NSDate date]] >
NSOrderedSame) {
NSTimeInterval theTime = [theNotification.fireDate
timeIntervalSinceReferenceDate] – 
self.startTimeOfCurrentDay;
self.clockControl.time = 
remainder(theTime, kSecondsOfDay / 2.0);
self.alarmHidden = NO;
}
else {
self.alarmHidden = YES;
}
[self updateTimeLabel];
}
Listing 3.45 Aktualisierung der Views aus den Benachrichtigungen
Sie berechnet aus der absoluten Alarmzeit und der Startzeit des aktuellen Tages die relative Alarmzeit zum aktuellen Tag und übergibt diesen Wert an das Clockcontrol. Außerdem sorgt sie durch einen Aufruf von updateTimeLabel dafür, dass der Controller auch die Textanzeige aktualisiert. Um diese Aktualisierung zu aktivieren, müssen Sie die Methode noch aus der Methode viewWillAppear: aus aufrufen:
- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
[self updateViews];
}
Listing 3.46 Aufruf der Aktualisierungsmethode
3.3.2 Benachrichtigungen verarbeiten
Durch eine lokale Benachrichtigung können Sie folgende Ereignisse auslösen. Sie können
- eine Nachricht anzeigen lassen,
- einen Klang mit einer maximalen Länge von 30 Sekunden abspielen und
- die Zahl in dem Badge, das am Icon der Applikation im Springboard erscheint, auf einen festen Wert setzen.
Die lokale Benachrichtigung kann indes nicht Ihre Applikation automatisch in den Vordergrund bringen. Entweder ist sie bereits im Vordergrund, oder abhängig von der Konfiguration erscheinen eine Nachricht und die Badgenummer und ertönt der Klang. Um die App zu aktivieren, muss der Nutzer den Button Anzeigen in der Alertbox betätigen beziehungsweise das Banner antippen.
Wenn die App im Vordergrund läuft, informiert Cocoa Touch Sie direkt über das Auftreten einer lokalen Benachrichtigung. Das geschieht über die Methode application:didReceiveLocalNotification: im Application-Delegate. Das Betriebssystem ruft diese Methode auch auf, wenn die App nicht oder im Hintergrund lief und der Nutzer die App über die Nachricht aktiviert hat. Sie können diese Methode nutzen, um auch dann eine Nachricht anzuzeigen, wenn die App während des Nachrichtenempfangs aktiv ist. Mit dieser Methode können Sie also die Benachrichtigung manuell verarbeiten. Das Beispielprogramm soll eine Alertbox anzeigen und einen Ton abspielen.
Allerdings sollten Sie die Alertbox auch nur dann anzeigen, wenn die App bereits aktiv war. Andernfalls sähe der Nutzer ja zwei Hinweise auf den Alarm direkt hintereinander, was er zumindest als störend oder sogar fehlerhaft ansehen könnte. Um diesen Effekt zu vermeiden, können Sie den Zustand der App beim Auslösen der Benachrichtigung über die Property applicationState des Singletons UIApplication unterscheiden. Nur wenn die App den Zustand UIApplicationStateActive hat, soll sie den Alarm auslösen.
Die Nachricht zeigen Sie über eine Alertbox an. Dazu verwenden Sie die Klasse UI-AlertView. Die Alertbox in Listing 3.47 zeigt nur die Nachricht und einen OK-Button an und lässt den Titel leer.
- (void)application:(UIApplication *)inApplication
didReceiveLocalNotification:
(UILocalNotification *)inNotification {
if(inApplication.applicationState ==
UIApplicationStateActive) {
UIAlertView *theAlert =
[[UIAlertView alloc] initWithTitle:nil
message:inNotification.alertBody
delegate:nil cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[theAlert show];
[theAlert release];
[self playSound];
}
}
Listing 3.47 Anzeige einer Alertbox beim Empfang einer Benachrichtigung
Sie können den Systemton für lokale Benachrichtigungen leider nicht selbst in Ihrem Programm abspielen. Es gibt dafür keine Funktion oder Methode, die genau diesen Klang ertönen lässt. Die Applikation soll stattdessen einen anderen Ton abspielen, was in der Methode playSound geschieht. Das Klingeln eines mechanischen Weckers passt hier sehr gut, und Sie finden die Datei ringtone.caf im Beispielprojekt auf der DVD. Sie können aber auch andere Töne verwenden, wenn sie im passenden Format vorliegen.
Die Töne müssen laut Dokumentation als 16-Bit-Little-Endian-PCM-Audiodaten im Core-Audio-Format mit der Endung .caf vorliegen und die Länge des Tons darf nicht größer als 30 Sekunden sein. Unter OS X gibt es das Kommandozeilenprogramm afconvert, mit dem Sie eine Audiodatei in dieses Format konvertieren können. Der Aufruf dafür ist:
/usr/bin/afconvert –f caff –d LEI16 {Eingabedatei} {Ausgabedatei}
Dabei ist der Parameter {Eingabedatei} die Datei mit dem Ton im Ursprungsformat, und das Programm schreibt den konvertierten Klang in die Datei {Ausgabedatei}. Liegt der Klingelton für den analogen Wecker beispielsweise als WAV-Datei vor, können Sie ihn mit
/usr/bin/afconvert -f caff -d LEI16 ringtone.wav ringtone.caf
in eine CAF-Datei konvertieren.
Bevor Sie den Klang verwenden können, müssen Sie diesen als Ressource zu Ihrem Projekt hinzufügen. Dazu ziehen Sie einfach die gewünschte Datei auf die Gruppe Supporting Files in der Navigatorspalte des Projekts. Sie sollten im folgenden Dialog darauf achten, dass Xcode die Datei auch in das Projekt kopiert und dem Target AlarmClock zuordnet. Setzen Sie dazu die Häckchen in den Checkboxen Destination und Add to targets. Sie können anschließend den Systemton bei der Erzeugung der lokalen Benachrichtigung auch durch die Sounddatei ersetzen, indem Sie den Dateinamen ohne Pfadangabe verwenden. Tauschen Sie dazu einfach die Zuweisung des Klangnamens in Listing 3.43 durch die folgende Anweisung aus:
theNotification.soundName = @"ringtone.caf";
Im Gegensatz zu dem Systemton können Sie diesen Klang auch über Funktionen aus der Audio-Toolbox abspielen., wozu Sie allerdings dieses Framework einbinden müssen. Wählen Sie dazu zunächst das Target der App aus. Ein Xcode-Projekt kann mehrere Produkte (zum Beispiel Apps oder Frameworks) verwalten. Jedes Produkt hat dabei sein eigenes Target. Es legt die Dateien und Regeln zur Erstellung des jeweiligen Produkts fest. Sie erreichen das Target, wenn Sie in der linken Navigatorspalte das Projekt auswählen. Daneben erscheint eine Spalte mit dem Abschnitt Targets. Wählen Sie dort das Target AlarmClock aus (siehe Abbildung 3.39, links).
Abbildung 3.39 So fügen Sie ein Framework zu einem Target hinzu.
Im Hauptfenster befindet sich dann ein Tabulator Build Phases mit dem Abschnitt Link Binary With Libraries. Öffnen Sie diesen Punkt durch einen Klick auf das entsprechende Dreieck. Sie können dem Target nun über das Plus-Zeichen neue Frameworks hinzufügen. Selektieren Sie im Dialog den Eintrag AudioToolbox. framework, und klicken Sie auf den Button Add.
Wenn Sie ein Framework einbinden, fügt Xcode sowohl die Binärdateien mit den entsprechenden Klassen, Methoden und Funktionen als auch die entsprechenden Suchpfade für Headerdateien mit den Deklarationen zum Projekt hinzu. Dabei liegen die Headerdateien immer unterhalb eines virtuellen Ordners mit dem Namen des Frameworks.
In der Regel hat jedes Framework von Apple eine Headerdatei mit dem Namen des Frameworks. Über diese Datei können Sie alle Header des Frameworks auf einmal einbinden. Da Sie die Audio-Toolbox im Application-Delegate verwenden möchten, müssen Sie dort den Header dieses Frameworks über die Direktive
#import <AudioToolbox/AudioToolbox.h>
einbinden.
Das Abspielen des Tons erfolgt in zwei Schritten. Zuerst muss ihn die App aus der Datei laden. Sie erhalten dabei einen Verweis auf den Ton, mit dem Sie ihn beliebig oft abspielen können. Wenn Sie ihn nicht mehr benötigen, sollten Sie den Ton über diesen Verweis auch wieder freigeben.
Der Verweis auf den Ton ist eine vorzeichenlose Zahl, die das Application-Delegate über die Property soundId verwaltet. Das Delegate soll den Ton jedoch nur einmal laden, und das nur dann, wenn es ihn braucht. Dies erfolgt in dem Lazy-Getter soundId. Diese Methode prüft zuerst, ob sie den Ton bereits geladen hat, und macht dies gegebenenfalls.
Lazy-Getter
Lazy-Getter sind eine einfache und effektive Möglichkeit für den sparsamen Umgang mit Ressourcen. Sie haben bereits ein weiteres Beispiel dafür kennengelernt. Die Property view der Klasse UIViewController lädt den View erst, wenn das Programm das erste Mal darauf zugreift. Das ist in der Regel kurz vor dessen Anzeige.
Lazy-Getter haben meist den gleichen, einfachen Aufbau:
- (MyClass *)myObject {
if(myObject == nil) {
myObject = ...; // Speicherverwaltung nicht vergessen!
}
return myObject;
}
Die Methode playSound spielt den Ton ab:
- (NSNumber *)soundId {
if(soundId == nil) {
NSURL *theURL =
[[NSBundle mainBundle] URLForResource:@"ringtone"
withExtension:@"caf"];
SystemSoundID theId;
if(AudioServicesCreateSystemSoundID(
(CFURLRef) theURL, &theId) ==
kAudioServicesNoError) {
self.soundId =
[NSNumber numberWithUnsignedInt:theId];
}
}
return soundId;
}
- (void)playSound {
NSNumber *theId = self.soundId;
if(theId) {
AudioServicesPlaySystemSound(
[theId unsignedIntValue]);
}
}
Listing 3.48 Laden und Abspielen eines Tons
Listing 3.48 verwendet für die Property soundId anstatt des Typs NSUInteger die Klasse NSNumber. Das hat den Vorteil, dass Sie über den Property-Wert nil den Zustand des nicht geladenen Tons abbilden können. In der Methode dealloc müssen Sie den Ton auch wieder freigeben, damit kein Speicherleck entsteht. Das können Sie durch den folgenden Setter erreichen:
- (void)setSoundId:(NSNumber *)inSoundId {
if(soundId != inSoundId) {
if(soundId != nil) {
AudioServicesDisposeSystemSoundID(
[soundId unsignedIntValue]);
[soundId release];
}
soundId = [inSoundId retain];
}
}
Listing 3.49 Freigabe des Alarmtons im Setter
Hier erfolgt der lesende Zugriff auf den Wert auch über das Attribut und nicht über den Getter. Der wäre hier kontraproduktiv, da er ja den Ton lädt, falls die App ihn noch nicht geladen hat. Diese Implementierung stellt außerdem sicher, dass das Delegate den alten Alarmton immer freigibt, wenn Sie einen neuen über den Setter setzen.
Das Application-Delegate kann über die Methode applicationDidReceiveMemoryWarning: auch auf Speicherwarnungen reagieren. Da es den Alarmton jederzeit wieder laden kann, sollten Sie in dieser Methode den Property-Wert für soundId auf nil setzen, um den Ton freizugegeben und dadurch Speicherplatz einzusparen.
- (void)applicationDidReceiveMemoryWarning:
(UIApplication *)inApplication {
self.soundId = nil;
}
Listing 3.50 Freigabe des Alarmtons bei einer Speicherwarnung
Ressourcen sind vielfältig
Auch der Alarmton stellt eine Ressource des Programms dar, die nach der Terminologie aus Abschnitt 3.2.11 wiederherstellbar ist. Dabei besteht zwar die Referenz auf den Ton nur aus einigen Bytes, die Tondaten dürften aber wesentlich größer sein.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.








Jetzt bestellen





