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

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

Download:
- ZIP, ca. 49,9 MB
Buch bestellen
Ihre Meinung?

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

Apps programmieren für iPhone und iPad
Galileo Computing
1000 S., geb., mit DVD
39,90 Euro, ISBN 978-3-8362-1915-0
Pfeil 5 Daten, Tabellen und Controller
Pfeil 5.1 Benachrichtigungen
Pfeil 5.1.1 Benachrichtigungen empfangen
Pfeil 5.1.2 Eigene Benachrichtigungen verschicken
Pfeil 5.2 Layoutanpassungen und Viewrotationen
Pfeil 5.2.1 Flexible Views dank Autosizing
Pfeil 5.2.2 Lang lebe das Rotationsprinzip!
Pfeil 5.2.3 Anpassung des Layouts
Pfeil 5.3 Autolayout
Pfeil 5.3.1 Restriktionen im Interface Builder festlegen
Pfeil 5.3.2 Restriktionen per Programmcode erstellen
Pfeil 5.3.3 Autolayout und Lokalisierung
Pfeil 5.4 Core Data
Pfeil 5.4.1 Datenmodellierung
Pfeil 5.4.2 Implementierung von Entitätstypen
Pfeil 5.4.3 Einbindung von Core Data
Pfeil 5.4.4 Der Objektkontext
Pfeil 5.4.5 Die Nachrichten des Objektkontexts
Pfeil 5.4.6 Anlegen und Ändern von Entitäten in der Praxis
Pfeil 5.5 Texte, Bilder und Töne verwalten
Pfeil 5.5.1 Die Tastatur betritt die Bühne
Pfeil 5.5.2 Fotos aufnehmen
Pfeil 5.5.3 Töne aufnehmen und abspielen
Pfeil 5.6 Tableviews und Core Data
Pfeil 5.6.1 Tableviews
Pfeil 5.6.2 Tabellenzellen gestalten
Pfeil 5.6.3 Tableviews unter iOS 5
Pfeil 5.6.4 Buttons in Tabellenzellen
Pfeil 5.6.5 Zellen löschen
Pfeil 5.7 Core Data II: Die Rückkehr der Objekte
Pfeil 5.7.1 Prädikate
Pfeil 5.7.2 Aktualisierung des Tableviews
Pfeil 5.7.3 Das Delegate des Fetchedresultscontrollers
Pfeil 5.7.4 Tabelleneinträge suchen
Pfeil 5.8 Twitter
Pfeil 5.8.1 Der Twitter-Account
Pfeil 5.8.2 Hinzufügen des Twitter-Frameworks
Pfeil 5.8.3 Integration in das Fototagebuch
Pfeil 5.9 Collectionviews
Pfeil 5.9.1 Der Collectionviewcontroller
Pfeil 5.9.2 Gitterdarstellung
Pfeil 5.9.3 Zellen und die Datenquelle
Pfeil 5.9.4 Ergänzende Views
Pfeil 5.9.5 Freie Layouts
Pfeil 5.9.6 Freie Layouts und ergänzende Views
Pfeil 5.9.7 Zellen auswählen
Pfeil 5.9.8 Decorationviews

Galileo Computing - Zum Seitenanfang

5.5 Texte, Bilder und Töne verwaltenZur nächsten Überschrift

Im Gegensatz zu Computern oder den Smartphones anderer Hersteller besitzen die iOS-Geräte keine Hardware-Tastatur. Stattdessen zeigt das Gerät eine Tastatur auf dem Bildschirm an, und der Touchscreen empfängt die Tastendrücke. Bei diesem Vorgehen entsteht das Problem, dass die Tastatur das Eingabefeld verdeckt. Die Lösung dafür ist eine Verschiebung des Eingabefelds in einen Bereich, den die Tastatur nicht verdeckt.


Galileo Computing - Zum Seitenanfang

5.5.1 Die Tastatur betritt die BühneZur nächsten ÜberschriftZur vorigen Überschrift

Für Texteingaben bietet Cocoa Touch die Klassen UITextField und UITextView an. Während Textfelder für die Eingabe von kurzen, meist einzeiligen Texten gedacht sind, eignen sich Textviews besonders für mehrzeilige und längere Texte. Das Verhalten beider Klassen ist allerdings sehr ähnlich. Allerdings ist UITextView im Gegensatz zu UITextField nicht von UIControl abgeleitet und versendet somit auch keine Ereignisse. Die Tastatur verhält sich bei beiden Klassen hingegen gleich. Wenn Sie ein Textfeld oder einen Textview antippen, fährt die Tastatur vom unteren Bildschirmrand aus.

Das Fototagebuch verwendet für die Texteingabe einen Textview, da die Beschreibung eines Eintrags durchaus ein längerer Text sein kann. Allerdings liegt der Textview so, dass ihn die Tastatur im Hochformat ganz und im Querformat teilweise verdeckt. Der Viewcontroller sollte also dafür sorgen, dass sich der Textview beim Erscheinen der Tastatur an den freien Bereich des Bildschirms anpasst.

Dazu müssen Sie zunächst mitbekommen, wann Sie die Ausmaße des Views verändern müssen. Dafür haben Sie zwei Möglichkeiten: Sie können entweder die Methoden textViewShouldBeginEditing: beziehungsweise textViewDidEndEditing: des Textview-Delegates verwenden, oder Sie arbeiten mit Benachrichtigungen. Die Verwendung der Delegate-Methoden ist naheliegend und findet sich in vielen Beispielprogrammen im Web. Allerdings beruht dieser Weg leider auf einem Denkfehler. Die beiden Methoden informieren Sie über den Beginn beziehungsweise das Ende des Editiervorgangs. Sie informieren Sie hingegen nicht über das Erscheinen oder Verschwinden der Tastatur. Das ist keine Spitzfindigkeit, denn auf einem iPad mit angeschlossener Hardware-Tastatur zeigt Cocoa Touch die Software-Tastatur nicht an.

Simulation der Hardware-Tastatur

Sie können das Verhalten Ihrer Apps mit einer angeschlossenen Hardware-Tastatur im Simulator testen. Dazu müssen Sie den Menüpunkt HardwareHardware-Tastatur simulieren einschalten. Der Simulator zeigt dann keine Tastatur an, wenn Sie auf ein Texteingabeelement klicken.

Cocoa Touch kann Sie jedoch auch über das Erscheinen und Verschwinden der Tastatur informieren. Das geschieht über die beiden Benachrichtigungen UIKeyboardWillShowNotification und UIKeyboardDidShowNotification, die Sie über das Erscheinen der Tastatur informieren; und entsprechend informieren UIKeyboardWillHideNotification und UIKeyboardDidShowNotification Sie über das Verschwinden der Tastatur.

Benachrichtigungen auch mit Hardware-Tastatur

Diese Benachrichtigungen verschickt iOS in bestimmten Fällen auch bei angeschlossener Hardware-Tastatur, und zwar dann, wenn das Eingabefeld einen Input-Accessory-View verwendet, das ist in Abbildung 5.26 die Werkzeugleiste mit dem Sichern- und Abbrechen-Button. Damit können Sie zusätzliche Eingabemöglichkeiten auf der Tastatur bereitstellen. Da diese natürlich nicht auf der Hardware-Tastatur vorhanden sind, zeigt Cocoa Touch diesen zusätzlichen View bei angeschlossener Hardware-Tastatur auf dem Display an. Sie sollten also auch bei einer angeschlossenen Tastatur darauf achten, dass auch der Input-Accessory-View nicht die Texteingabe verdeckt.

Um die Änderung der Tastaturanzeige mitzubekommen, registriert sich die Klasse ItemViewController für zwei Benachrichtigungen in der Methode viewDidAppear:

NSNotificationCenter *theCenter = Zeilenumbruch
[NSNotificationCenter defaultCenter];
[theCenter addObserver:self Zeilenumbruch
selector:@selector(keyboardWillAppear:) Zeilenumbruch
name:UIKeyboardWillShowNotification object:nil];
[theCenter addObserver:self Zeilenumbruch
selector:@selector(keyboardWillDisappear:) Zeilenumbruch
name:UIKeyboardWillHideNotification object:nil];

Listing 5.25 Registrierung für Tastaturbenachrichtigungen

Die Benachrichtigung liefert im User-Info-Dictionary mehrere Informationen. Über den Schlüssel UIKeyboardFrameEndUserInfoKey können Sie die Koordinaten und die Größe der Tastatur relativ zu den Koordinaten des Fensters ermitteln. Da die Struktur CGRect jedoch keine Objective-C-Klasse ist, kann ein Dictionary diese Werte nicht direkt aufnehmen. Cocoa Touch verpackt sie deswegen in ein Objekt der Klasse NS-Value, und über deren Methode CGRectValue erhalten Sie das Rechteck.

Die Methode keyboardWillAppear: rechnet zunächst dieses Rechteck in das Koordinatensystem des Hauptviews um. Aus diesem Rechteck berechnet sie dann das freie Rechteck oberhalb der Tastatur. Das ist genau der Platz, den der Textview während des Editiervorgangs belegen soll. Sie müssen dieses Rechteck noch in das Koordinatensystem des Superviews des Textviews umrechnen, da ja dessen Frame relativ dazu ist.

- (void)keyboardWillAppear:(NSNotification *)inNotification {
NSValue *theValue = [inNotification.userInfo Zeilenumbruch
valueForKey:UIKeyboardFrameEndUserInfoKey];
UIView *theView = self.view;
CGRect theFrame = [theView.window convertRect:
[theValue CGRectValue] toView:theView];

theFrame = CGRectMake(0.0, 0.0, Zeilenumbruch
CGRectGetWidth(self.view.frame), Zeilenumbruch
CGRectGetMinY(theFrame));
theFrame = [theView convertRect:theFrame Zeilenumbruch
toView:textView.superview];
[UIView beginAnimations:nil context:nil];
self.textView.frame = theFrame;
[UIView commitAnimations];
}

Listing 5.26 Koordinatenanpassung beim Erscheinen der Tastatur

Zur Konvertierung eines Rechtecks von dem Koordinatensystem eines Views in das Koordinatensystem eines anderen können Sie die Methode convertRect:toView: verwenden, die das Rechteck im ersten Parameter aus dem Koordinatensystem des Empfängers in das Koordinatensystem des Views im zweiten Parameter umrechnet. Bei dieser Umrechnung bleibt die absolute Lage des Rechtecks gleich. Das heißt, wenn Sie die Rechtecke [theValue CGRectValue] im Fenster und das Ergebnisrechteck im View theView zeichnen würden, liegen beide exakt aufeinander. Die Methode rechnet alle Verschiebungen, Drehungen und Skalierungen entsprechend um.

Abbildung 5.25 zeigt die möglichen Anzeigezustände der Tastatur und des Accessory-Views. Im ausgeblendeten Zustand 1 befinden sich sowohl die Tastatur als auch der Accessory-View unterhalb des Fensters im nicht sichtbaren Bereich. Ohne Hardware-Tastatur schiebt Cocoa Touch den Accessory-View und die Software-Tastatur in den sichtbaren Bereich 2. Mit angeschlossener Hardware-Tastatur zeigt Cocoa Touch nur den Accessory-View an 3.

Abbildung

Abbildung 5.25 Die möglichen Anzeigezustände der Tastatur

Das Listing verwendet für die Berechnung die Y-Koordinate des Tastaturrechtecks (in Abbildung 5.25 ist es durch einen schwarzen Punkt dargestellt) und nicht dessen Höhe. Wenn eine Hardware-Tastatur angeschlossen ist, zeigt Cocoa Touch die Software-Tastatur nicht an. Das Rechteck hat indes trotzdem die volle Größe – also Input-Accessory-View und Software-Tastatur. Der View wird also nur so weit hochgeschoben, dass nur der Input-Accessory-View zu sehen ist.

Listing 5.26 zeigt die Koordinatenberechnung. Darin weist die vorletzte Anweisung das berechnete Rechteck dem Textview als neuen Frame zu. Diese Zuweisung befindet sich in einem Animationsblock, den die Klassenmethoden beginAnimations:context: und commitAnimations: begrenzen.

Mit diesem Konstrukt können Sie sehr einfach implizite Animationen erzeugen. Sie brauchen dabei nur den neuen Wert einer animierbaren Property in einem Animationsblock zu setzen. Cocoa Touch führt dann die notwendigen Schritte für die Animation automatisch durch, indem es zwischen dem alten und dem neuen Wert der Property interpoliert. Kapitel 6, »Models, Layer, Animationen«, geht noch genauer auf Animationen ein.

Die Tastatur verschwindet

Wenn iOS die Tastatur vom Bildschirm entfernt, soll der Textview wieder seine alte Position zugewiesen bekommen. Dieses Rechteck können Sie allerdings nicht über das Tastaturrechteck berechnen. In vielen Fällen müssen Sie sich die ursprünglichen Koordinaten merken. Im Beispielprogramm haben die Ränder des Textviews feste Abstände zu den Rändern seines Superviews. Die Methode keyboardWillDisappear: macht sich diesen Umstand zunutze und berechnet aus den Bounds des Superviews den Frame des Textviews:

- (void)keyboardWillDisappear:Zeilenumbruch
(NSNotification *)inNotification {
[UIView beginAnimations:nil context:nil];
textView.frame = CGRectInset(Zeilenumbruch
textView.superview.bounds, 10.0, 10.0);
[UIView commitAnimations];
}

Listing 5.27 Koordinatenanpassung beim Verschwinden der Tastatur

Dazu verwendet die Methode die Funktion CGRectInset. Sie berechnet aus dem angegebenen Rechteck und einem horizontalen sowie vertikalen Abstand ein neues Rechteck. Es liegt innerhalb des ursprünglichen Rechtecks und hat zu diesem jeweils die gewünschten horizontalen und vertikalen Abstände.

Sie können jetzt beim Ausblenden der Tastatur die ursprünglichen Koordinaten des Textviews wiederherstellen. Aber wie bringen Sie Cocoa Touch dazu, die Tastatur verschwinden zu lassen? Textfelder und Textviews besitzen dafür kein Standardeingabeelement. Viele Apps verwenden dafür die Returntaste mit der Beschriftung Done.

Das Beispielprogramm verwendet hierfür den bereits erwähnten Input-Accessory-View, da sich hierdurch mehrere Möglichkeiten bieten. Diesen Hilfsview zeigt Cocoa Touch immer oberhalb der Tastatur an. Das Fototagebuch benutzt dafür eine Werkzeugleiste – das ist ein View mit der Klasse UIToolbar; Sie können jedoch auch beliebige andere Views verwenden.

Abbildung

Abbildung 5.26 Tastatur mit Input-Accessory-View

Einen Input-Accessory-View erstellen Sie getrennt von der View-Hierarchie und weisen ihn der Property inputAccessoryView des Textfelds oder Textviews zu. In der Szene des Itemviewcontrollers im Storyboard finden Sie auf der obersten Ebene einen View der Klasse UIToolBar, der als Accessory-View dient. Um ihn mit dem Textview zu verbinden, können Sie die Werkzeugleiste über eine Outlet-Property mit dem Viewcontroller verbinden und ihn in der Methode viewDidLoad des Controllers der Property inputAccessoryView des Textviews zuweisen.

Sie können sich durch einen kleinen Trick dieses zusätzliche Outlet sparen. Die Property inputAccessoryView ließe sich ja mit der Werkzeugleiste schon im Interface Builder verbinden, wenn sie eine Outlet-Property wäre. Diesen Mangel können Sie jedoch relativ einfach durch eine Kategorie beheben. Das Beispielprojekt enthält dafür die Kategoriedeklaration UITextView(Outlets) in der Datei UITextView+Outlets.h (siehe Listing 5.28).

@interface UITextView(Outlets)
@property(readwrite, retain) IBOutlet Zeilenumbruch
UIView *inputAccessoryView;
@end

Listing 5.28 Nachträgliche Deklaration eines Outlets

Eine Implementierung dieser Kategorie ist nicht notwendig, da die Klasse ja bereits diese Property implementiert. Falls Sie die Kategorie dagegen erweitern möchten und eine Implementierung für andere Methoden brauchen, sollten Sie die Property dort über @dynamic inputAccessoryView definieren, damit Sie keine Compilerwarnungen erhalten. Wenn Sie jetzt den Verbindungsinspektor des Textviews im Interface Builder öffnen, finden Sie dort auch ein Outlet für den Input-Accessory-View. Der Interface Builder erkennt diese Deklaration automatisch und erweitert den Verbindungsinspektor entsprechend.

Die Werkzeugleiste enthält drei Elemente: Jeweils einen Button zum Sichern und Abbrechen sowie einen unsichtbaren Zwischenraum. Der Zwischenraum sorgt dafür, dass die Werkzeugleiste den Abbrechen-Button an den rechten Rand drückt. Die Buttons sind mit den Action-Methoden saveText: beziehungsweise revertText: verbunden. Beide Methoden lassen dabei die Tastatur über den Aufruf der Methode endEditing: verschwinden.

- (IBAction)saveText:(id)inSender {
[self.view endEditing:YES];
self.item.text = self.textView.text;
[self saveItem];
}

- (IBAction)revertText:(id)inSender {
[self.view endEditing:YES];
self.textView.text = self.item.text;
}

Listing 5.29 Methoden zum Beenden der Texteingabe

Die Methode saveText: weist den Inhalt des Textviews der Property text des Tagebucheintrags zu und speichert ihn über die Methode saveItem. Wenn Sie hingegen die Texteingabe abbrechen, stellt die Methode den ursprünglichen Text im Textview aus dem Text des Tagebucheintrags wieder her.


Galileo Computing - Zum Seitenanfang

5.5.2 Fotos aufnehmenZur nächsten ÜberschriftZur vorigen Überschrift

Neben einem Text kann ein Tagebucheintrag auch ein Bild enthalten. Der Eintrag enthält jedoch die Bilddaten nicht in einem Attribut. Er verweist über die Relationship media auf weitere Entitäten, die Bilder, Töne oder auch andere Medieninhalte aufnehmen können. Die Entitäten des Typs Medium enthalten nicht nur die Binärdaten des Mediums, sondern auch dessen Medientyp.

Die Vorgehensweise, die Mediendaten von den Daten des Eintrags zu trennen, hat einige Vorteile. Der wichtigste ist, dass die Mediendaten nicht automatisch mit den Eintragsdaten geladen werden müssen. Die Bild- und Tondaten belegen selbst in komprimierter Form schnell einige hundert Kilobyte, was bei einem größeren Tagebuch schnell zu Laufzeit- und Speicherproblemen führen kann. Außerdem ist diese Struktur flexibel. Sie können relativ einfach neue Medientypen (z. B. für Videos) hinzufügen, ohne das Datenmodell ändern zu müssen. Vielleicht wollen Sie die App auch so verändern, dass sie mehrere Bilder pro Tagebucheintrag speichern kann. Auch das lässt sich über diese Struktur relativ einfach verwirklichen.

In der hier vorgestellten Version gibt es hingegen zu jedem Medientyp nur jeweils ein Medienobjekt pro Eintrag. Deshalb besitzt die Klasse DiaryEntry zwei Methoden, die den Zugriff über den Medientyp erlauben. Die Methode mediumForType: liefert zu einem Medientyp das passende Objekt, während die Methode removeMediumForType: das zum Medientyp passende Objekt löscht. Außerdem gibt es noch eine Methode zum Hinzufügen und Entfernen eines Mediums. Diese vier Methoden sind dank Core Data mit wenigen Codezeilen wie folgt implementiert:

- (Medium *)mediumForType:(NSString *)inType {
for(Medium *theMedium in self.media) {
if([[theMedium type] isEqualToString:inType]) {
return theMedium;
}
}
return nil;
}
- (void)removeMediumForType:(NSString *)inType {
Medium *theMedium = [self mediumForType:inType];

[self removeMedium:theMedium];
}
- (void)addMedium:(Medium *)inMedium {
if(inMedium != nil) {
[self removeMediumForType:inMedium.type];
[self addMediaObject:inMedium];
}
}
- (void)removeMedium:(Medium *)inMedium {
if(inMedium != nil) {
inMedium.diaryEntry = nil;
[self.managedObjectContext deleteObject:inMedium];
}
}

Listing 5.30 Verwaltung der Medien über den Tagebucheintrag

Damit haben Sie die Grundlage für die Verwaltung der Medien eines Tagebucheintrags. Diese Implementierung stellt sicher, dass es immer nur maximal ein Medium eines Typs zu einem Eintrag gibt. Wenn Sie beispielsweise ein zweites Foto zu einem Eintrag über die Methode addMedium: hinzufügen, löscht sie automatisch den vorhandenen Eintrag.

Zugriff auf die Kamera

Damit Sie Bilder in das Tagebuch bekommen, müssen Sie die Kamera des iPhones ansprechen. Das geschieht am einfachsten über die Klasse UIImagePickerController, mit der Sie relativ einfach Bilder oder Filme aufnehmen können. Diese Klasse ist ein fertiger Viewcontroller, den Sie nur noch nach Ihren Wünschen konfigurieren und anzeigen brauchen. Außerdem müssen Sie natürlich nach der Auswahl oder Aufnahme das Bild zum Tagebucheintrag hinzufügen.

Der Imagepicker unterstützt drei Eingabequellen:

  • UIImagePickerControllerSourceTypeCamera: die im Gerät eingebauten Kameras
  • UIImagePickerControllerSourceTypeSavedPhotosAlbum: das Fotoalbum mit den Aufnahmen der Kamera – also eine virtuelle Filmpatrone der Kamera. Wenn das Gerät keine Kamera besitzt, zeigt der Controller alle Alben des Geräts an.
  • UIImagePickerControllerSourceTypePhotoLibrary: alle auf dem Gerät vorhandenen Fotoalben inklusive der Filmpatrone, sofern das Gerät eine Kamera besitzt

Wenn Sie den Imagepicker verwenden möchten, sollten Sie zuerst prüfen, ob das Gerät die gewünschte Eingabequelle besitzt. Falls Sie diesen Schritt unterlassen, kann der Zugriff auf eine nicht vorhandene Quelle zum Absturz Ihrer App führen. Die Klasse UIImagePickerController stellt für den Test die Klassenmethode isSourceTypeAvailable: bereit. Sie übergeben ihr als Parameter eine der drei oben aufgeführten Konstanten und erhalten YES als Ergebnis, wenn die entsprechende Quelle existiert.

Der Itemviewcontroller überprüft die vorhandenen Eingabequellen des Imagepickers in der Methode viewDidLoad. In Abhängigkeit vom Ergebnis aktiviert beziehungsweise deaktiviert er die entsprechenden Buttons für die jeweilige Eingabequelle. Diese beiden Anweisungen finden Sie in Listing 5.31, das auch die Anweisungen für die Erzeugung des Imagepickers enthält. Die Methode setzt außerdem die Property allowsEditing, damit Sie die Größe und den Ausschnitt der Bilder im Picker bearbeiten können.

self.imagePicker = [[UIImagePickerController alloc] init];
self.imagePicker.allowsEditing = YES;
self.imagePicker.modalTransitionStyle = Zeilenumbruch
UIModalTransitionStyleFlipHorizontal;
self.imagePicker.delegate = self;
self.cameraButton.enabled = Zeilenumbruch
[UIImagePickerController isSourceTypeAvailable:Zeilenumbruch
UIImagePickerControllerSourceTypeCamera];
self.photoLibraryButton.enabled =
[UIImagePickerController isSourceTypeAvailable:Zeilenumbruch
UIImagePickerControllerSourceTypePhotoLibrary];
if([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
self.popoverController = [[UIPopoverController alloc]
initWithContentViewController:self.imagePicker];
}

Listing 5.31 Initialisierung des Imagepickers und Prüfung der Eingabequellen

Auf einem iPhone oder einem iPod müssen Sie den Imagepicker als modalen View anzeigen, während Sie auf dem iPad ein Pop-over dafür verwenden müssen. Sie haben hierfür leider keine Wahl (und deshalb auch keine Qual). Deswegen legt die Methode viewDidLoad auf dem iPad einen Pop-over-Controller an und speichert ihn in der Property popoverController. Auf dem iPhone oder dem iPod touch bleibt der Wert dieser Property hingegen immer nil. Die Methode showPickerWithSourceType:barButtonItem: zeigt schließlich den Imagepicker an:

- (void)showPickerWithSourceType:Zeilenumbruch
(UIImagePickerControllerSourceType)inTypeZeilenumbruch
barButtonItem:(UIBarButtonItem *)inItem {
if([UIImagePickerController isSourceTypeAvailable:inType]) {
self.imagePicker.sourceType = inType;
if(self.popoverController == nil) {
[self presentViewController:self.imagePicker Zeilenumbruch
animated:YES];
}
else if(!self.popoverController.popoverVisible) {
[self.popoverController
presentPopoverFromBarButtonItem:inItem
permittedArrowDirections:
UIPopoverArrowDirectionAny
animated:YES];
}
}
}

Listing 5.32 Anzeige des Imagepickers

Im Gegensatz zum modalen View des Imagepickers auf dem iPhone oder iPod touch schließt das iPad das Pop-over automatisch, wenn Sie ein Bild aufnehmen oder auswählen beziehungsweise den Dialog abbrechen. Auf den beiden erstgenannten Gerätetypen müssen Sie den Imagepickercontroller also explizit schließen. Dazu dient im Fototagebuch die Hilfsmethode dismissImagePickerController: der Klasse ItemViewController, die den Picker nur schließt, wenn die App ihn nicht in einem Popup geöffnet hat.

- (void)dismissImagePickerController:Zeilenumbruch
(UIImagePickerController *)inPicker {
if(self.popoverController == nil) {
[inPicker dismissViewControllerAnimated:YES Zeilenumbruch
completion:NULL];
}
}

Listing 5.33 Schließen des Imagepickers

Die Methode aus Listing 5.33 rufen Sie aus den Delegate-Methoden des Imagepickers auf.

Verarbeitung von Aufnahmen

Der Imagepicker bietet für die Verarbeitung der Aufnahmen das Delegate-Protokoll UIImagePickerDelegate an. Über die Methode imagePickerControllerDidCancel: teilt Ihnen der Controller mit, dass der Anwender die Aufnahme beziehungsweise Bildauswahl abgebrochen hat. Nach einer erfolgreichen Aufnahme oder Bildauswahl ruft der Imagepicker hingegen die Delegate-Methode imagePickerController:didFinishPickingMediaWithInfo: auf. Dieser Methode übergibt der Picker im zweiten Parameter ein Dictionary, das das Bild und weitere Daten enthält. Für die Einträge im Dictionary gibt es vordefinierte Konstanten:

  • UIImagePickerControllerMediaType ist der Schlüssel des Medientyps der Aufnahme. Dieser Wert entspricht einer der Konstanten kUTTypeImage (Bild) oder kUTTypeMovie (Film).
  • UIImagePickerControllerOriginalImage verweist auf das unbearbeitete Bild der Klasse UIImage der Aufnahme.
  • UIImagePickerControllerMediaMetadata enthält ein weiteres Dictionary mit Metadaten des Bildes. Diesen Eintrag unterstützen allerdings nur die iOS-Versionen ab 4.1.
  • UIImagePickerControllerMediaURL existiert ebenfalls erst seit iOS 4.1 und enthält eine URL auf den Eintrag des Fotoalbums, den Sie ausgewählt haben.

Wenn Sie das Bearbeiten der Bilder im Imagepicker erlaubt haben, erhalten Sie noch Einträge über die Ausschnittsauswahl:

  • UIImagePickerControllerEditedImage enthält das bearbeitete Bild der Aufnahme – also den vom Anwender gewählten Ausschnitt.
  • UIImagePickerControllerCropRect enthält ein CGRect in einem NSValue-Objekt mit dem Auswahlrechteck.

Objekte der Klasse UIImage können Sie nicht direkt in Core Data speichern, sondern Sie müssen sie dazu erst serialisieren. Das geht am einfachsten, wenn Sie das Bild in ein bekanntes Format umwandeln. Cocoa Touch stellt dafür die beiden Funktionen UIImageJPEGRepresentation und UIImagePNGRepresentation zur Verfügung, die Bilder in JPEG- beziehungsweise PNG-Daten umwandeln.

Das Fototagebuch liest in der Delegate-Methode das bearbeitete Bild aus und wandelt es mit der Funktion UIImageJPEGRepresentation in JPEG-Daten um. Diese Funktion hat zwei Parameter: Der erste ist das UIImage-Objekt, und der zweite ist ein Fließkommawert zwischen 0 (niedrig) und 1 (hoch), der die Bildqualität nach der JPEG-Komprimierung beschreibt. Die Funktion liefert ein Objekt der Klasse NSData mit den Bilddaten zurück. Diese Objekte können Sie problemlos in Core-Data speichern.

Dazu dient die Property data der Klasse Medium. Im Beispielprogramm sorgt die Methode updateMediumData:withMediumType: dafür, dass das Programm die Mediendaten immer richtig aktualisiert. Da die App sie auch für die Speicherung der Audiodaten verwendet, erhält sie als zweiten Parameter den Typ der Daten – also eine der beiden Konstanten kMediumTypeImage oder kMediumTypeAudio:

- (void)updateMediumData:(NSData *)inDataZeilenumbruch
withMediumType:(NSString *)inType {
if(inData.length == 0) {
[self.item removeMediumForType:inType];
}
else {
Medium *theMedium = [NSEntityDescriptionZeilenumbruch
insertNewObjectForEntityForName:@"Medium"Zeilenumbruch
inManagedObjectContext:self.managedObjectContext];

theMedium.type = inType;
theMedium.data = inData;
[self.item addMedium:theMedium];
}
[self saveItem];
}

Listing 5.34 Speicherung von Mediendaten

Die Implementierung der Delegate-Methode finden Sie in Listing 5.35. Die Delegate-Methode liest das bearbeitete Bild aus dem Info-Dictionary, aktualisiert die Bildanzeige und sichert die Bilddaten. Außerdem schließt die Methode, wie bereits besprochen, den Imagepicker.

- (void)imagePickerController:Zeilenumbruch
(UIImagePickerController *)inPickerZeilenumbruch
didFinishPickingMediaWithInfo:(NSDictionary *)inInfo {
UIImage *theImage = [inInfo Zeilenumbruch
valueForKey:UIImagePickerControllerEditedImage];
NSData *theData = UIImageJPEGRepresentation(Zeilenumbruch
theImage, 0.8);
CGSize theSize = [theImage sizeToAspectFitInSize:Zeilenumbruch
CGSizeMake(60.0, 60.0)];

[self updateMediumData:theData Zeilenumbruch
withMediumType:kMediumTypeImage];
self.imageView.image = theImage;
theImage = [theImage scaledImageWithSize:theSize];
self.item.icon = UIImageJPEGRepresentation(theImage, 0.8);
[self dismissImagePickerController:inPicker];
}

Listing 5.35 Abspeichern der Aufnahme

Die Tabellenansicht des Fototagebuchs stellt die Aufnahmen in einem kleineren Format (60 × 60 Pixel) dar. Die App erzeugt die Bilddaten für dieses Icon, indem sie das aufgenommene Bild entsprechend verkleinert und ebenfalls in JPEG-Daten umwandelt. Da die App das Icon für die Tabellenansicht braucht, legt sie dessen Daten jedoch nicht als Medien ab, sondern in der Property icon des Tagebucheintrags.

Die Skalierung des Bildes geschieht über zwei Methoden der Kategorie UIImage(ImageTools):

  • sizeToAspectFitInSize:
  • scaledImageWithSize:

Die Methode sizeToAspectFitInSize: berechnet die Größe des Icons, sodass das Bild komplett in die angegebene Größe passt, wobei das Seitenverhältnis erhalten bleibt. Wenn Sie bei der Größenänderung eines Bildes das Verhältnis von dessen Breite zur Höhe erhalten, ist das Ergebnis nicht verzerrt – also gestreckt oder gestaucht.

- (CGSize)sizeToAspectFitInSize:(CGSize)inSize {
CGSize theSize = self.size;
CGFloat theWidthFactor = inSize.width / theSize.width;
CGFloat theHeightFactor = inSize.height / theSize.height;
CGFloat theFactor = fmin(theWidthFactor, theHeightFactor);

return CGSizeMake(theSize.width * theFactor, Zeilenumbruch
theSize.height * theFactor);
}

Listing 5.36 Größenberechnung mit festem Seitenverhältnis

Die Methode in Listing 5.36 berechnet jeweils den Skalierungsfaktor, um das Bild auf die Breite beziehungsweise die Höhe der Zielgröße inSize zu bringen. Wenn Sie das Rechteck mit diesen beiden Faktoren skalieren, entstehen zwei neue Rechtecke, von denen eines genau die Breite und das andere genau die Höhe des Zielrechtecks hat. Von diesen beiden Rechtecken passt nun genau das mit dem kleineren Skalierungsfaktor in das Zielrechteck.

Abbildung

Abbildung 5.27 Größenberechnung mit festem Seitenverhältnis

Sie können ein Beispiel dafür in Abbildung 5.27 sehen. Das Seitenverhältnis der Breite des Zielrechtecks zu der Breite des Bildrechtecks ist 3:4 (0,75) und analog für die Höhen 1:2 (0,5). Wie Sie an der Abbildung sehen können, passt das Rechteck mit dem kleineren Skalierungsfaktor 1:2 genau in das Zielrechteck.

Die zweite Methode scaledImageWithSize: der Kategorie berechnet ein skaliertes Bild, das genau die angegebene Größe hat. Dafür verwendet sie Core-Graphics-Funktionen und einen Image-Kontext, den sie über die Funktion UIGraphicsBeginImageContext erzeugt. Sie können auf diesen Kontext die gleichen Operationen anwenden, die Sie bereits im dritten Kapitel kennengelernt haben. Im Gegensatz zum Grafikkontext aus dem dritten Kapitel erfolgen die Ausgaben des Image-Kontexts hingegen in ein Bild.

- (UIImage *)scaledImageWithSize:(CGSize)inSize {
CGRect theFrame = CGRectMake(0.0, 0.0, Zeilenumbruch
inSize.width, inSize.height);
UIImage *theImage;
UIGraphicsBeginImageContext(inSize);
[self drawInRect:theFrame];
theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

Listing 5.37 Skalierung eines Bildes

Listing 5.37 erstellt einen Image-Kontext in der Größe inSize. Die Funktion liefert die Referenz auf den Kontext leider nicht direkt zurück. Sie können sie jedoch auch hier über die Funktion UIGraphicsGetCurrentContext ermitteln. Core Graphics verwaltet seine Kontexte in einem Stapel, weswegen Sie den Image-Kontext über die Funktion UIGraphicsEndImageContext() am Ende der Methode wieder freigeben müssen. Das Bild zeichnen Sie über die Methode drawInRect: in den Grafikkontext. Das Bild des Grafikkontexts können Sie schließlich über die Funktion UIGraphicsGetImageFromCurrentImageContext des UIKits erhalten.


Galileo Computing - Zum Seitenanfang

5.5.3 Töne aufnehmen und abspielenZur nächsten ÜberschriftZur vorigen Überschrift

Anders als für Bilder und Videos gibt es unter iOS keine grafische Standardkomponente für die Aufnahme von Tönen. Dieser Abschnitt beschreibt eine mögliche Umsetzung eines einfachen Controllers für diesen Zweck. Da dieser Controller nicht den kompletten Bildschirm überdeckt, verwendet die App für die Anzeige den Übergang SubviewSegue und die Klasse SubviewController. Während der Übergang sehr stark der Klasse RaisingSegue aus dem letzten Kapitel ähnelt, dient die Controller-Klasse als Basis für Subviewcontroller. Den Code für diese beiden Klassen finden Sie im Projekt auf der DVD oder im Git-Repository.

Dateiinformation

Den Quellcode dieser beiden Klassen finden Sie auf der DVD unter Code/Apps/Storyboard/PhotoDiary/SubviewController.m und Code/Apps/Storyboard/PhotoDiary/SubviewSegue.m oder im Github-Repository zum Buch unter https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/Storyboard/PhotoDiary/PhotoDiary/SubviewController.m und https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/Storyboard/PhotoDiary/PhotoDiary/SubviewSegue.m.

Da wir diesen Code bereits in Kapitel 4, »Alles unter Kontrolle«, erläutert haben, verzichten wir hier auf eine Beschreibung.

Play it again, Sam: Töne abspielen

In Kapitel 3, »Views und Viewcontroller«, haben Sie bereits eine Möglichkeit kennenlernt, Töne als Systemsound abzuspielen. Diese Wiedergabemöglichkeit ist allerdings für das Fototagebuch aus mehreren Gründen nicht geeignet. Sie benötigen beispielsweise eine URL, die auf die Sounddatei verweist. Die Töne des Fototagebuchs liegen jedoch in Attributen von Core-Data-Entitäten, für die es keine brauchbaren URLs gibt. Wenn Sie einen Ton über die Funktion AudioServicesPlaySystemSound abspielen, können Sie die Wiedergabe nicht steuern. Sie können die Wiedergabe weder pausieren lassen noch an eine bestimmte Stelle springen. Es ist nur die Wiedergabe des kompletten Tons vom Anfang bis zum Ende möglich.

Über die Klasse AVAudioPlayer des AVFoundation-Frameworks bietet das iOS eine weitere Möglichkeit, Töne wiederzugeben. Diese Klasse erlaubt eine wesentlich flexiblere Wiedergabe von Audiodaten. Zwar ist der Audioplayer auch eine Controllerkomponente, jedoch ist er kein Viewcontroller. Das Fototagebuch enthält aus diesem Grund eine Subviewcontroller-Klasse AudioPlayerController, die einen Audioplayer kapselt.

Abbildung

Abbildung 5.28 Der Audioplayer-Controller

Der View des Audioplayer-Controllers zeigt in der Mitte einen Slider an, der die aktuelle Abspielposition wiedergibt, und Sie können über den Slider auch die Abspielposition wählen. Ein Label daneben zeigt die genaue Abspielzeit in Sekunden an. Darüber zeigt er den aktuellen Lautstärkepegel grafisch an. Am unteren Rand befinden sich noch zwei Buttons, um den Player zu schließen und die Wiedergabe zu starten beziehungsweise zu unterbrechen. Da die Initialisierung des Audioplayers einige Sekunden benötigen kann, besitzt der View für die Überbrückung der Wartezeit auch noch einen Activityindicator, auf den wir später noch genauer eingehen.

Die Initialisierung des Audioplayers erfolgt in der Methode startAudioPlayer, die Sie in Listing 5.38 sehen.

- (void)startAudioPlayer {
NSError *theError = nil;
AVAudioPlayer *thePlayer = [[AVAudioPlayer alloc]Zeilenumbruch
initWithData:self.audioMedium.data error:&theError];

if(thePlayer == nil) {
NSLog(@"playAudio: %@", theError);
self.loading = NO;
}
else {
self.audioPlayer = thePlayer;
thePlayer.delegate = self;
thePlayer.meteringEnabled = YES;
self.time = slider.value;
slider.maximumValue = thePlayer.duration;
self.loading = NO;
[self updateTime:nil];
[self startTimer];
[thePlayer play];
}
}

Listing 5.38 Erzeugung und Initialisierung eines Audioplayers

Im Fehlerfall liefert die Initializer-Methode initWithData:error: des Audioplayers im zweiten Parameter einen Verweis auf einen Fehler zurück, und die Variable thePlayer verweist dann auf nil. Im Fehlerfall schreibt die Methode lediglich eine Log-Ausgabe.

Da der Controller den Lautstärkepegel anzeigen soll, müssen Sie über die Property meteringEnabled die Messung dieser Werte einschalten. Die Länge des Tons können Sie über die Property duration des Audioplayers ermitteln, um damit den maximalen Wert des Sliders zu setzen. Da der Controller sowohl den Slider als auch die Anzeige des Lautstärkepegels regelmäßig aktualisieren muss, startet er dafür einen Timer. Die Aktualisierung dieser Werte erfolgt in der Methode updateTime:.

- (void)updateTime:(NSTimer *)inTimer {
NSTimeInterval theTime = self.audioPlayer.currentTime;

[self.audioPlayer updateMeters];
self.meterView.value = Zeilenumbruch
[self.audioPlayer averagePowerForChannel:0];
self.slider.value = theTime;
[self updateTimeLabel];
}

Listing 5.39 Aktualisierung der Lautstärkepegel- und Positionsanzeige

Der Audioplayer misst den Lautstärkepegel für jeden Kanal getrennt. Um die aktuellen Werte zu erhalten, müssen Sie zuerst die Methode updateMeters aufrufen, bevor Sie den Wert für die einzelnen Kanäle über averagePowerForChannel: abrufen. Der Rückgabewert dieser Methode liegt zwischen –160 und 0. Die Klasse MeterView rechnet diesen Wert in ihrer Methode drawRect: um und zeichnet abhängig von dem Wert ein Bild flexibler Breite.

- (void)drawRect:(CGRect)inRect {
CGRect theBounds = self.bounds;
CGFloat theValue = theBounds.size.width * self.value / WIDTH;
UIImage *theImage = [UIImage imageNamed:@"meter.png"];
theBounds.size.width += theValue;
[theImage drawAsPatternInRect:theBounds];
}

Listing 5.40 Zeichnen des Lautstärkepegels in der Klasse »MeterView«

Die Methode zeichnet den Balken, indem sie über die Methode drawAsPatternInRect: eine gekachelte Darstellung des Bildes erzeugt. Sie zeichnet dabei das Bild so oft nebeneinander, bis sie die gesamte Fläche bedeckt hat, die das Rechteck theBounds beschreibt.

Tipp

Vielleicht sind Ihnen die abgerundeten Ecken der Lautstärkepegelanzeige aufgefallen. Diese Darstellung können Sie relativ elegant über den Layer des Views realisieren, der eine Property cornerRadius besitzt. Außerdem müssen Sie das Clipping einschalten. Die Klasse MeterView setzt diese Werte in der Methode awakeFromNib.

- (void)awakeFromNib {
[super awakeFromNib];
self.layer.cornerRadius = 5;
self.layer.masksToBounds = YES;
}

Im nächsten Kapitel gehen wir genauer auf die Möglichkeiten der Layer ein.

Die Methode updateTime: aktualisiert außerdem den Wert des Sliders, um die aktuelle Abspielposition anzuzeigen. Sie liest dazu die Zeit über die Property currentTime aus und setzt diesen Wert in die Property value des Sliders.

Aktivitätsanzeige und länger laufende Operationen

Der Audioplayer benötigt für das Laden und Initialisieren der Audiodaten einige Sekunden Zeit. Der Nutzer sollte in dieser Zeit eine Rückmeldung von der App erhalten, dass sie beschäftigt ist. Dazu verwendet sie eine Aktivitätsanzeige. Das ist ein View der Klasse UIActivityIndicator, die zwölf im Kreis angeordnete graue Striche darstellt. Sie können über die Methode startAnimating eine Animation dieses Views starten, sodass die Striche reihum jeweils die Helligkeit ändern. Das Stoppen der Animation erfolgt über die Methode stopAnimating.

Abbildung

Abbildung 5.29 Audioplayer mit Aktivitätsanzeige

In der Klasse AudioPlayerController erfolgt die Steuerung der Animation über die Property loading, deren Zugriffsmethoden auf dem Animationszustand der Aktivitätsanzeige basieren.

- (BOOL)loading {
return self.activityIndicator.isAnimating;
}

- (void)setLoading:(BOOL)inLoading {
if(inLoading) {
[self.activityIndicator startAnimating];
}
else {
[self.activityIndicator stopAnimating];
}
}

Listing 5.41 Steuerung der Aktivitätsanzeige

Wenn Sie die Property hidesWhenStopped des Activity-Indikators auf YES setzen, zeigt Cocoa Touch den Indikator nur während der Animation an. Einen Indikator ohne Animation versteckt Cocoa also automatisch. Die Animation beginnt allerdings nicht sofort durch den Aufruf der Methode startAnimating, sondern erst, nachdem die Programmausführung in die Runloop des Threads zurückgekehrt ist. Das können Sie dadurch erreichen, dass Ihr Programm alle Methoden verlässt, die Sie erstellt haben.

Animationen und Nebenläufigkeit

Wenn das iPhone parallel zu einer länger laufenden Operation, wie beispielsweise bei der Initialisierung des Audioplayers, eine Animation zeigen soll, dann muss es zwei Dinge gleichzeitig erledigen. Die Verarbeitung von Animationen erfolgt in Cocoa Touch in der Runloop, sodass der folgende Code nicht funktionieren kann:

// So bitte nicht
[self.activityIndicator startAnimating];
// lange laufende Operationen ausführen
[self.activityIndicator stopAnimating];

Wenn Sie die Aktivitätsanzeige so verwenden, friert die Anzeige zunächst ein, zeigt dann kurz den laufenden Indikator und lässt ihn anschließend verschwinden.

Das bedeutet für den Audioplayer, dass die Ausführung alle Ihre Methoden verlassen muss, bevor Sie den Audioplayer initialisieren. Das können Sie durch einen verzögerten Aufruf der Methode erreichen:

[self performSelector:@selector(startAudioPlayer) Zeilenumbruch
withObject:nil afterDelay:0.0];

Die Methode perfomSelector:withObject:afterDelay: stellt die Klasse NSObject bereit. Sie ruft die Methode mit dem angegebenen Selektor und dem Parameterobjekt im Empfänger nach der angegebenen Verzögerung auf. Diese Methode fügt den Methodenaufruf immer in die Runloop ein – auch bei einer Verzögerung von 0 Sekunden. Cocoa Touch bekommt so die Möglichkeit, die Animation der Aktivitätsanzeige zu aktivieren.

Der richtige Start einer Animation sieht also so aus:

- (void)startLongOperation {
[self.activityIndicator startAnimating];
[self performSelector:@selector(doLongOperation) Zeilenumbruch
withObject:nil afterDelay:0.0];
}
- (void)doLongOperation {
// lange laufende Operation ausführen
[self.activityIndicator stopAnimation];
}

Listing 5.42 Nebenläufige Operation parallel mit Animation ausführen

Animationen und Zeichnen

Cocoa Touch führt alle Zeichen- und Animationsoperationen über die Runloop aus. Diese Methoden markieren die Objekte für die entsprechende Operation. Wenn die Reihenfolge von diesen Operationen im Zusammenhang mit anderen Operationen wichtig ist, müssen Sie also zwischendurch die Runloop zum Zuge kommen lassen. Dazu können Sie im einfachsten Fall eine Struktur wie in Listing 5.42 verwenden. Die Methode performSelector:withObject: eignet sich hierfür allerdings nicht, da sie die Methode direkt ausführt.

Denken Sie an den Doppelpunkt im Selektor, wenn Sie einen Parameter an die Methode mit der lange laufenden Operation übergeben wollen.

Die Rückkehr in die Runloop hat indes noch einen weiteren Nebeneffekt. Da sie den Autoreleasepool verwaltet, veranlasst sie ihn, bei der Rückkehr an alle enthaltenen Objekte ein release zu schicken. Objekte, die nur der Autorelease-Pool hält, erleben also den Aufruf der verzögerten Methode nicht. Um die Speicherverwaltung zu vereinfachen, sendet die Methode performSelector:withObject:afterDelay: automatisch ein retain an den Nachrichtenempfänger und das Parameterobjekt. Nach der Ausführung der verzögerten Methode bekommen diese beiden Objekte ein release geschickt. Für die Parameter können Sie also ruhig Objekte im Autoreleasepool verwenden.

Wenn die Operation mehr als 3 bis 5 Sekunden dauern kann, sollten Sie diese in einem eigenen Thread ausführen, um den Hauptthread nicht zu blockieren. Eine einfache Möglichkeit, um eine Methode in einem Hintergrundthread auszuführen, bietet die Methode performSelectorInBackground:withObject: der Klasse NSObject. Auch diese Methode hält den Empfänger und das Parameterobjekt im Speicher. Die aufgerufene Methode muss immer einen eigenen Autoreleasepool für die Speicherverwaltung bereitstellen.

Wenn Sie aus dieser Methode Änderungen am View vornehmen möchten, sollten Sie diese Operationen allerdings im Hauptthread ausführen. Sie können dazu die Methode performSelectorOnMainThread:withObject:waitUntilDone: verwenden.

Tipp

Sie können über die performSelector-Varianten auch Methoden ohne Parameter aufrufen. Verwenden Sie dazu einfach den Selektor der Methode und den Parameterwert nil. Wenn Sie mehrere Parameterwerte übergeben möchten, können Sie diese in ein Dictionary einfügen und es als Parameter übergeben. Die Methode kann dann die einzelnen Parameterwerte aus dem Dictionary wieder extrahieren.

- (void)startVeryLongTask {
NSDictionary *theParameters = [NSDictionary
dictionaryWithObjectsAndKeys:[NSDate date], @"date",
..., nil];
[self.activityIndicator startAnimating];
[self performSelectorInBackground:@selector(veryLongTask:)
withObject:theParameters];
}
- (void)veryLongTask:(NSDictionary *)inParameters {
@autoreleasepool {
NSDate *theDate = [inParameters objectForKey:@"date"];
...
[self.textLabel performSelectorOnMainThread:Zeilenumbruch
@selector(setText:) withObject:theTextZeilenumbruch
waitUntilDone:NO];
...
[self.activityIndicatorZeilenumbruch
performSelectorOnMainThread:Zeilenumbruch
@selector(stopAnimating) withObject:nilZeilenumbruch
waitUntilDone:NO];
}
}

Listing 5.43 Starten eines Hintergrundtasks

Listing 5.43 stellt die Ausführung einer Methode in einem eigenen Thread schematisch dar. Der Hintergrundtask führt die Methode veryLongTask: aus, die den notwendigen Autoreleasepool zur Verfügung stellt.

Die Übergabe der Parameter erfolgt über ein Dictionary, das die Methode startVeryLongTask entsprechend verpackt. Die Methode veryLongTask: kann dann die Parameter aus diesem Dictionary wieder auslesen. Die Methode aktualisiert außerdem ein Label und stoppt die Aktivitätsanzeige, bevor sie endet. Diese beiden Aufrufe führt sie über den Hauptthread durch, da sie die Anzeige aktualisieren.

Achtung, Aufnahme: Töne aufzeichnen

Auch für Tonaufnahmen gibt es eine fertige Controller-Komponente in Cocoa Touch. Das ist die Klasse AVAudioRecorder aus dem AVFoundation-Framework. Wie der Audioplayer ist auch der Audiorecorder kein Viewcontroller, und Sie müssen eine Bedienungsoberfläche erstellen. Das Fototagebuch besitzt dafür einen Subviewcontroller der Klasse AudioRecorderController für Audio-Aufnahmen, dessen View dem View des Audioplayer-Controllers ähnelt.

1, 2, 3 – hallo, hallo – 1, 2, 3...

Bevor Sie eine Audio-Aufnahme unter iOS starten, sollten Sie überprüfen, ob das Gerät dazu auch in der Lage ist. Dazu stellt das Singleton AVAudioSession die Methode inputIsAvailable bereit. Im Beispielprojekt sperrt der Itemviewcontroller den Button für den Aufruf des Recorders, wenn keine Audio-Aufnahme möglich ist. Das geschieht über die folgende Zuweisung in viewDidLoad:

self.recordButton.enabled = Zeilenumbruch
[[AVAudioSession sharedInstance] inputIsAvailable];


Da Sie bei einer Aufnahme nicht die Aufnahmeposition festlegen können, verwendet der Recorder einen Fortschrittsbalken anstatt eines Sliders. Der Fortschrittsbalken zeigt Ihnen während der Aufnahme den Anteil der verbrauchten Aufnahmezeit an. Damit die Audiodaten nicht zu groß werden können, begrenzt der Controller die maximale Aufnahmezeit auf 30 Sekunden, die die Konstante kMaximalRecordingTime festlegt. Sie brauchen also nur den Wert dieser Konstanten verändern, um eine andere maximale Aufnahmezeit festzulegen.

Abbildung

Abbildung 5.30 Der Audiorecorder-Controller

Der Controller erzeugt den Audiorecorder in der Methode startRecording. Sie müssen bei der Erzeugung eine URL auf eine Datei im Dateisystem angeben, in die der Recorder die aufgenommenen Daten schreibt.

- (void)startRecorder {
NSError *theError = nil;
AVAudioRecorder *theRecorder = [[AVAudioRecorder alloc] Zeilenumbruch
initWithURL:self.fileURLZeilenumbruch
settings:self.audioRecorderSettings error:&theError];
if(theRecorder == nil) {
NSLog(@"startRecording: %@", theError);
self.preparing = NO;
}
else {
theRecorder.delegate = self;
self.audioRecorder = theRecorder;
if([self.audioRecorderZeilenumbruch
recordForDuration:kMaximalRecordingTime]) {
[self updateRecordButton];
[self startTimer];
self.preparing = NO;
}
}
}

Listing 5.44 Anlegen und Initialisieren des Audiorecorders

Da Ihre App unter iOS in einer Sandbox läuft, können Sie die Datei nicht einfach irgendwo in das Dateisystem des Geräts schreiben. Sie müssen erst ein Verzeichnis ermitteln, in das Sie die Datei schreiben können. Da Sie die Audiodatei nicht länger speichern müssen, können Sie Datei in das Verzeichnis für temporäre Dateien schreiben. Um das zu ermitteln, verwenden Sie die Funktion NSTemporaryDirectory aus dem Foundation-Framework.

- (NSString *)filePath {
NSString *theDirectory = NSTemporaryDirectory();

return [theDirectory stringByAppendingPathComponent:Zeilenumbruch
@"recording.caf"];
}

- (NSURL *)fileURL {
return [NSURL fileURLWithPath:self.filePath];
}

Listing 5.45 URL-Bestimmung für die Audiodatei

Dateiablage unter iOS

Sie sollten keine Dateien in das Ressourcenverzeichnis Ihrer App schreiben. Das funktioniert zwar im Simulator, unter iOS ist dieser Ordner hingegen geschützt, und der schreibende Zugriff führt zu einem Programmfehler.

Sie müssen dem Audiorecorder bei der Initialisierung außerdem noch das Audioformat beschreiben, in dem Sie aufzeichnen möchten. Hier gibt es eine Fülle von möglichen Parametern, die sich wahrscheinlich nicht alle sinnvoll in einer Methodensignatur unterbringen lassen. Aus diesem Grund bekommt der Initialisierer diese Parameter in einem Dictionary, das die Methode audioRecorderSettings erzeugt. Das Fototagebuch nimmt die Töne mit einem Kanal (Mono) in dem Format IMA 4:1 ADPCM von Apple mit der Sampling-Rate 16000 auf, was einen guten Kompromiss zwischen Qualität und Dateigröße darstellt.

- (NSDictionary *)audioRecorderSettings {
return [NSDictionary dictionaryWithObjectsAndKeys:Zeilenumbruch
[NSNumber numberWithInt:kAudioFormatAppleIMA4],Zeilenumbruch
AVFormatIDKey,
[NSNumber numberWithFloat:16000.0], AVSampleRateKey,Zeilenumbruch
[NSNumber numberWithInt:1], AVNumberOfChannelsKey,Zeilenumbruch
nil];
}

Listing 5.46 Erzeugung der Parameter für die Audio-Aufnahme

Der Recorder hält die Aufnahme entweder nach 30 Sekunden [Das ist der Wert der Konstanten kMaximalRecordingTime in Listing 5.44.] automatisch an oder indem das Programm die Methode pause aufruft. Nach der Aufnahme müssen Sie die Daten aus der Datei in ein NSData-Objekt lesen, wozu Sie den Convenience-Konstruktor dataWithContentsOfURL: verwenden können. Dieses Datenobjekt können Sie dann der bereits besprochenen Methode updateMediumData:withMediumType: übergeben, um es an den Tagebucheintrag anzuhängen. Der Aufruf erfolgt in der Delegate-Methode audioRecorder:didRecordToData: im Itemviewcontroller.

Die Aktualisierung der Fortschritts-, der Lautstärkepegel- und der Zeitanzeige erfolgt analog zum Audioplayer über einen Timer.



Ihr Kommentar

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







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

Apps programmieren für iPhone und iPad
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa





 Einstieg in
 Objective-C 2.0
 und Cocoa


Zum Katalog: Apps entwickeln für iPhone und iPad - Videotraining






 Apps entwickeln für
 iPhone und iPad -
 Videotraining


Zum Katalog: Apps mit HTML5 und CSS3






 Apps mit HTML5
 und CSS3


Zum Katalog: iPhone- und iPad-Apps entwickeln






 iPhone- und
 iPad-Apps entwickeln


Zum Katalog: Android 4






 Android 4


Zum Katalog: Android-Apps entwickeln - Videotraining






 Android-Apps
 entwickeln -
 Videotraining


Zum Katalog: Windows Store Apps mit XAML und C#






 Windows Store Apps
 mit XAML und C#


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





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


[Galileo Computing]

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