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

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

Buch bestellen
Ihre Meinung?

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

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 9 Multimedia
Pfeil 9.1 Schönschrift
Pfeil 9.1.1 Texthervorhebungen über Attributed Strings
Pfeil 9.1.2 Weitere Anzeigemöglichkeiten
Pfeil 9.1.3 Text mit Hervorhebungen über Dokumente erzeugen
Pfeil 9.1.4 Zeichenketten in Farben umwandeln
Pfeil 9.2 Einbindung von HTML-Dokumenten
Pfeil 9.2.1 Anzeige von HTML-Dokumenten
Pfeil 9.2.2 Javascript-Dateien einbinden
Pfeil 9.2.3 Das Delegate des Webviews
Pfeil 9.2.4 Webviews und Scrollviews
Pfeil 9.2.5 Der Viewport
Pfeil 9.2.6 Dynamische HTML-Seiten
Pfeil 9.2.7 HTML-Sonderzeichen maskieren
Pfeil 9.2.8 Javascript ausführen
Pfeil 9.2.9 Ereignisübergabe an die Applikation
Pfeil 9.3 Antwortcaching und Offlinemodus
Pfeil 9.3.1 Bilder nachladen
Pfeil 9.3.2 Cache Me If You Can
Pfeil 9.3.3 Let’s go offline
Pfeil 9.3.4 Protokolle
Pfeil 9.3.5 Ein datenbankbasierter Offlinecache
Pfeil 9.4 Videos
Pfeil 9.4.1 YouTube-Videos einbetten
Pfeil 9.4.2 Wiedergabe über das Media-Player-Framework
Pfeil 9.4.3 Vorschaubilder erzeugen
Pfeil 9.4.4 Videos über Layer anzeigen

Rheinwerk Computing - Zum Seitenanfang

9.4VideosZur nächsten Überschrift

An der Beispielapplikation fehlt bislang noch ein kleines Detail: Sie kann noch keine Videos abspielen. Dazu gibt es unter iOS mehrere Möglichkeiten, wobei Filme von Videoplattformen wie YouTube, Vimeo oder Clipfish einen Sonderfall darstellen. Videoplattformen verwenden in der Regel einen Flash-Player und übertragen das Video per Streaming an den Client-Rechner, und beides unterstützt iOS leider nicht.


Rheinwerk Computing - Zum Seitenanfang

9.4.1YouTube-Videos einbettenZur nächsten ÜberschriftZur vorigen Überschrift

YouTube bietet dennoch eine relativ einfache Möglichkeit, seine Videos auch unter iOS einzubetten. Der Webserver erkennt den Client anhand des Headers User-Agent, und liefert für iOS spezielle HTML-Seiten aus, die MP4-Filme über Video-Tags einbetten. Somit lassen sich YouTube-Videos über Webviews anzeigen. Die entsprechenden URLs dazu finden Sie im ersten Dictionary unter dem Schlüssel »href« in dem Array unter dem Schlüssel »link« im Dictionary des Items. Den entsprechenden Code dazu sehen Sie in Listing 9.98.

- (void)collectionView:(UICollectionView *)inCollectionView 
didSelectItemAtIndexPath:(NSIndexPath *)inIndexPath {
YouTubeWebViewController *theController =
[self.storyboard
instantiateViewControllerWithIdentifier:@"webView"];
NSDictionary *theItem = self.items[inIndexPath.row];
NSArray *theLinks = theItem[@"link"];
NSString *theLink = [theLinks[0] objectForKey:@"href"];
NSURL *theURL = [NSURL URLWithString:theLink];

theController.title =
[theItem valueForKeyPath:@"title.$t"];
theController.url = theURL;
[self.navigationController
pushViewController:theController animated:YES];
}

Listing 9.98 Auslesen und Anzeigen einer YouTube-Video-URL

Allerdings zeigt der Webview nicht nur das Video, sondern die komplette YouTube-Seite mit ähnlichen Videos, Kommentaren und vielem mehr an (siehe Abbildung 9.17). Diese störenden Inhalte können Sie durch die Verwendung von Einbettungs-URLs vermeiden. Diese URLs dienen dazu, Videos über einen IFrame (siehe Abschnitt 9.2.9, »Ereignisübergabe an die Applikation«) in beliebige Webseiten einzubinden, und sie haben die Form http://www.youtube.com/embed/<Id>. Dabei ist <Id> eine eindeutige, alphanumerische Kennung des Videos.

Abbildung

Abbildung 9.17 Videoeinbindung über den Link aus den JSON-Daten

Abbildung

Abbildung 9.18 Videoeinbindung über Einbettungs-URL

Leider liefert YouTube die Einbettungs-URLs nicht mit den JSON-Daten aus. Da sie jedoch einen einheitlichen Aufbau haben, ist die Erstellung sehr einfach. Sie müssen dazu lediglich die Kennung des Films ermitteln, was Sie über die in Listing 9.98 verwendete URL erreichen, da sie die Kennung unter dem Parameter v enthält. Zu diesem Zweck erweitern Sie die Klasse NSURL über die Kategorie NSURL(Extensions) um die Methode queryParametersWithEncoding:.

- (NSDictionary *)queryParametersWithEncoding: 
(NSStringEncoding)inEncoding {
NSMutableDictionary *theParameters =
[NSMutableDictionary dictionary];
NSArray *theTuples =
[self.query componentsSeparatedByString:@"&"];

for(NSString *theItem in theTuples) {
NSArray *theTuple = [theItem
componentsSeparatedByString:@"="];
NSString *theKey = [theTuple[0]
stringByReplacingPercentEscapesUsingEncoding:
inEncoding];
NSString *theValue = [theTuples count] < 2 ? @"" :
[theTuple[1]
stringByReplacingPercentEscapesUsingEncoding:
inEncoding];
NSArray *theValues =
[theParameters objectForKey:theKey];

if(theValues == nil) {
theValues = @[theValue];
}
else {
theValues =
[theValues arrayByAddingObject:theValue];
}
[theParameters setValue:theValues forKey:theKey];
}
return [theParameters copy];
}

Listing 9.99 Parameter aus einer URL extrahieren

Die Methode in Listing 9.99 zerteilt zunächst die Zeichenkette mit den Parametern, die sie über die Methode query von NSURL erhält, anhand der &-Zeichen in die einzelnen Parameter der Form Schlüssel=Wert. Die Schleife zerlegt dann diese Komponenten jeweils in deren Schlüssel- und Wertteil und wandelt die Escape-Sequenzen wieder in die entsprechenden Zeichen um. Da eine URL mehrere Parameter mit dem gleichen Namen enthalten darf, legt die Methode die Parameterwerte immer in Arrays im Ergebnis-Dictionary ab.

Mit Hilfe dieser Kategoriemethode lässt sich nun die Methode aus Listing 9.98 so umschreiben, dass sie die Einbettungs-URL erzeugt und deren Inhalt im Webview anzeigt. Listing 9.100 stellt die notwendigen Änderungen hervorgehoben dar.

- (void)collectionView:(UICollectionView *)inCollectionView 
didSelectItemAtIndexPath:(NSIndexPath *)inIndexPath {
YouTubeWebViewController *theController =
[self.storyboard
instantiateViewControllerWithIdentifier:@"webView"];
NSDictionary *theItem = self.items[inIndexPath.row];
NSArray *theLinks = theItem[@"link"];
NSString *theLink = [theLinks[0] objectForKey:@"href"];
NSURL *theURL = [NSURL URLWithString:theLink];
NSDictionary *theParameters = [theURL
queryParametersWithEncoding:NSUTF8StringEncoding];
NSString *theId = [theParameters[@"v"] objectAtIndex:0];

theController.title =
[theItem valueForKeyPath:@"title.$t"];
theLink = [NSString stringWithFormat:
@"http://www.youtube.com/embed/%@", theId];
theController.url = [NSURL URLWithString:theLink];
[self.navigationController
pushViewController:theController animated:YES];
}

Listing 9.100 Erzeugung der Einbettungs-URL

Durch diese Änderung zeigt die App nun das Video auf der kompletten Fläche des Webviews an.


Rheinwerk Computing - Zum Seitenanfang

9.4.2Wiedergabe über das Media-Player-FrameworkZur nächsten ÜberschriftZur vorigen Überschrift

Für die Wiedergabe von YouTube-Videos ist die Anzeige über Webviews die zuverlässigste Möglichkeit. Falls die Videos jedoch in einem iOS-kompatiblen Format vorliegen, bietet sich die Darstellung über das Media-Player-Framework an, das mit der Klasse MPMoviePlayerController die direkte Anzeige eines Videos in einem View erlaubt. Dieser Controller bietet darüber hinaus wesentlich mehr Steuerungsmöglichkeiten als der HTML-Videoplayer, ermöglicht den Zugriff auf relevante Daten wie Abspielzeitpunkt, Dauer, Abmessungen und unterstützt die Erstellung von Einzelbildern aus dem geladenen Video. Der Controller unterstützt dabei die folgenden Videoformate:

  • H.264 Baseline Profile Level 3.0 Video mit bis zu 640 × 480 Pixeln und 30 fps
  • MPEG-4 Part 2

Dabei kann er die Videodaten während des Abspielens sowohl aus dem Dateisystem als auch aus dem Internet laden.

Videostreaming

Falls Sie Ihre Videos während des Abspielens aus dem Internet laden wollen, sollten Sie auch die geringeren Bandbreiten von WWANs berücksichtigen. Zum einen weist Apple Apps zurück, die zu große Datenmengen über das mobile Datennetz laden. Zum anderen ist es auch nicht sinnvoll und macht wenig Spaß, ein HD-Movie über eine GSM-Verbindung anzuschauen.

Eine Möglichkeit ist natürlich, in der App den Video-Download in mobilen Datennetzen zu unterbinden. Als Alternative bietet sich HTTP-Live-Streaming an, das geringere Videoqualitäten – und somit eine niedrigere Datenrate – für geringere Netzbandbreiten verwendet. Einen umfangreichen Überblick über diese Technologie finden Sie in der Technical Note TN2224, »Best Practices for Creating and Deploying HTTP Live Streaming Media for the iPhone and iPad« [Anm.: http://developer.apple.com/library/ios/#technotes/tn2224/_index.html] , von Apple.

Das Beispielprojekt für diesen Abschnitt enthält einen Videoplayer für einen Film, der sich im Ressourcenverzeichnis der App befindet. Neben der Anzeige des Films besitzt es eine Bookmark-Verwaltung, die eine einfaches Merken und Wiederfinden von Abspielpositionen erlaubt.

Projektinformation

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

Dieses Projekt enthält den Animationsfilm »Elephants Dream« von Bassam Kurdali (Regie) und Ton Roosendaal (Produktion) [Anm.: © Copyright 2006, Blender Foundation/Netherlands Media Art Institute – http://www.elephantsdream.org] , der unter einer Creative-Commons-Lizenz [Anm.: http://orange.blender.org/blog/creative-commons-license-2/] steht.

Objekte der Klasse MPMoviePlayerController verwalten zwar jeweils einen View zur Darstellung eines Films, sind jedoch keine Viewcontroller in dem Sinne, wie Sie sie bisher kennengelernt haben, da MPMoviePlayerController keine Unterklasse von UIViewController ist. Für die Anzeige eines Films über einen Movieplayer-Controller müssen Sie ein Objekt erstellen, konfigurieren und dessen View in eine bestehende View-Hierarchie integrieren.

Das neue Objekt initialisieren Sie über die Methode initWithContentURL:. Wenn Sie den Film direkt abspielen möchten, können Sie die Nachricht play an den Controller senden. Alternativ können Sie auch den Controller das erste Bild des Films anzeigen lassen, ohne die Wiedergabe zu startet. Dazu müssen Sie über die Methode prepareToPlay die Wiedergabe vorbereiten und über die Property shouldAutoplay den automatischen Start verhindern. Diese Schritte finden Sie in Listing 9.101.

NSURL *theURL = [[NSBundle mainBundle] 
URLForResource:@"elephants-dream" withExtension:@"mp4"];
MPMoviePlayerController *theController =
[[MPMoviePlayerController alloc]
initWithContentURL:theURL];

self.moviePlayerController = theController;
[theController prepareToPlay];
theController.shouldAutoplay = NO;

Listing 9.101 Erzeugung und Konfiguration eines Movieplayers

Nach der Erzeugung und Konfiguration des Movieplayer-Controllers müssen Sie seinen View, den Sie über die Property view erhalten, in eine View-Hierarchie einbetten. Dem View sollten Sie allerdings die entsprechenden Koordinaten zuweisen, damit er auch an der richtigen Position erscheint. Das Beispielprojekt verwendet einfach die Bounds des Superviews als Frame des Movieplayer-Views, da er die komplette Fläche bedecken soll. Es ist außerdem sinnvoll, die Autoresizingmask anzupassen, damit sich der View bei Größenänderungen seines Superviews entsprechend anpasst.

theController.view.frame = self.view.bounds;
theController.view.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.view addSubview:theController.view];

Listing 9.102 Movieplayer-View in View-Hierarchie einfügen

Vereinfachte Anzeige von Filmen

Falls Sie einen Film auf der kompletten Bildschirmfläche anzeigen möchten, können Sie auch die Unterklasse MPMoviePlayerViewController von UIViewController verwenden. Sie können diesen Viewcontroller wie jeden anderen Viewcontroller über Tabbar-, Navigation- oder andere Container-Viewcontroller anzeigen. Für die modale Darstellung erweitert die Kategorie UIViewController(MPMoviePlayerViewController) die Klasse UIViewController um die Methoden presentMoviePlayerViewControllerAnimated: und dismissMoviePlayerViewControllerAnimated, die spezielle Transitionen für das Erscheinen und Verschwinden des Movieplayer-Viewcontrollers verwenden.

Die Anzeige eines Films sieht damit wie folgt aus:

NSURL *theMovieURL = ...;
UIViewController *theController = ...;

MPMoviePlayerViewController *theMovieController =
[[MPMoviePlayerViewController alloc]
initWithContentURL:theMovieURL];

[theController presentMoviePlayerViewControllerAnimated:
theMovieController];

Der View des Movieplayer-Controllers zeigt nicht nur das Video, sondern auch Informations- und Steuerungselemente an (siehe Abbildung 9.19), die er während des Abspielens allerdings ausblendet. Die ausgeblendeten Elemente lassen sich jedoch durch ein Antippen des Players während des Abspielens einblenden. Außerdem unterstützt der Movieplayer einen Fullscreen-Modus, bei dem das angezeigte Video die komplette Fläche des Bildschirms einnimmt.

Abbildung

Abbildung 9.19 Der Movieplayer-Controller in der Beispiel-App

Der Nutzer kann diesen Modus durch einen Button in der Steuerungsleiste (in Abbildung 9.20 unten rechts) aktivieren. Sie können den Fullscreen-Modus über die Property fullscreen abfragen und setzen. Für einen animierten Übergang in den beziehungsweise vom Fullscreen-Modus können Sie die Methode setFullscreen:animated: verwenden.

Abbildung

Abbildung 9.20 Die Bookmark-Verwaltung der Beispiel-App

Das Beispielprojekt besitzt eine einfache Bookmark-Verwaltung, die Positionen beziehungsweise Zeitpunkte in einem Array in den User-Defaults speichert. Über den Plus-Button oben links in der Navigationsleiste kann der Nutzer den Zeitpunkt der aktuellen Abspielposition zu dem Array in den User-Defaults hinzufügen. Diesen Zeitpunkt können Sie über die Property currentPlaybackTime des Movieplayer-Controllers lesen und auch schreiben. Wenn Sie den Wert schreiben, bewegen Sie die Abspielposition an den übergebenen Zeitpunkt.

Das Anlegen eines Bookmarks ist erst dann sinnvoll, wenn der Movieplayer-Controller das Video geladen hat. Aus diesem Grund deaktiviert die App während des Ladens den Plus-Button. Der Movieplayer-Controller stellt diese und eine Reihe anderer Informationen über Benachrichtigungen bereit.

Tabelle 9.10 Wichtige Benachrichtigungen des Movieplayer-Controllers

Benachrichtigung Beschreibung

MPMoviePlayerLoadState-DidChangeNotification

Versendet der Player, wenn sich der Ladezustand des Videos geändert hat. Den aktuellen Ladezustand können Sie über die Property loadState des Controllers abfragen.

MPMoviePlayerDidEnter-FullscreenNotification,
MPMoviePlayerDidExit-FullscreenNotification

Markieren den Beginn beziehungsweise das Ende des Fullscreen-Modus.

MPMoviePlayerPlayback-DidFinishNotification

Diese Benachrichtigung können Sie verwenden, um das Ende der Videowiedergabe mitzubekommen.

MPMovieDurationAvailable-Notification

Der Controller teilt Ihnen nach dem Laden eines neuen Videos über diese Benachrichtigung mit, dass er dessen Abspieldauer kennt. Diese Abspielzeit können Sie über die Property duration abfragen.

MPMoviePlayerThumbnailImageRequestDidFinishNotification

Für jedes Vorschaubild, das Sie beim Movieplayer anfordern, erhalten Sie eine Nachricht dieses Typs mit dem Ergebnis.

Die Benachrichtigungen enthalten dabei jeweils den Movieplayer-Controller als Absendeobjekt. Die Applikation verarbeitet die MPMoviePlayerLoadStateDidChangeNotification wie in Listing 9.103 gezeigt: Die Methode moviePlayerLoadStateDidChange: liest den aktuellen Ladezustand des Videos aus der Property loadState des Controllers und prüft, ob eines der Bits MPMovieLoadStatePlayable oder MPMovieLoadStatePlaythroughOK gesetzt ist. Beide Bits zeigen an, dass das Video abspielbereit ist, wobei das zweite zusätzlich bedeutet, dass bei Video-Downloads genügend Daten für einen längeren Abspielzeitraum bereitstehen.

- (void)moviePlayerLoadStateDidChange: 
(NSNotification *)inNotification {
MPMoviePlayerController *theController =
inNotification.object;
MPMovieLoadState theState = theController.loadState;

self.addBookmarkButton.enabled = (theState &
(MPMovieLoadStatePlayable |
MPMovieLoadStatePlaythroughOK)) != 0;
}

Listing 9.103 Verarbeitung des Load-States


Rheinwerk Computing - Zum Seitenanfang

9.4.3Vorschaubilder erzeugenZur nächsten ÜberschriftZur vorigen Überschrift

Für die Verwaltung der Bookmarks verwendet die App einen Collectionview, der jeweils ein Vorschaubild und den Zeitpunkt der Bookmarks anzeigt (siehe Abbildung 9.20). Dabei erzeugt die App das Vorschaubild über die Methode requestThumbnailImagesAtTimes:timeOption: der Klasse MPMoviePlayerController, die ein Bild mit der Klasse UIImage aus dem Video ermittelt, das der Position des angegebenen Zeitpunkts entspricht. Dabei verwendet die Methode den genauen Zeitpunkt, wenn Sie für den Parameter timeOption die Konstante MPMovieTimeOptionExact verwenden. Alternativ können Sie auch den Wert MPMovieTimeOptionNearestKeyFrame angeben. Dann verwendet der Player eine Position, an der er das Bild leichter berechnen kann, womit er weniger Zeit dafür im Gegensatz zur exakten Berechnung benötigt.

Die Methode requestThumbnailImagesAtTimes:timeOption: erzeugt die Bilder allerdings nicht synchron, sondern asynchron, d. h., statt das Ergebnis direkt zurückzugeben, sendet sie eine Benachrichtigung des Typs mit dem Bild oder dem Fehler.

Synchrone Erzeugung von Vorschaubildern

Die Klasse MPMoviePlayerController bietet über die Methode thumbnailImageAtTime:timeOption: auch eine Möglichkeit zur synchronen Erzeugung von Vorschaubildern. Allerdings hat Apple diese Methode mit iOS 7 als veraltet (deprecated) markiert. Sie können und dürfen diese Methode zwar noch nutzen, allerdings kann es passieren, dass Apple diese Methode in zukünftigen iOS-Versionen nicht mehr bereitstellt. Es ist also besser, veraltete Methoden nicht mehr zu verwenden.

Der Bookmarks-Viewcontroller muss sich also zuerst für Benachrichtigungen des Typs MPMoviePlayerThumbnailImageRequestDidFinishNotification registrieren. Das geschieht in der Methode viewWillAppear: der Klasse BookmarksViewController über die Methode addObserverForName:object:queue:usingBlock:. Diese Registrierung funktioniert analog zu addObserver:selector:name:object: mit dem Unterschied, dass das Notificationcenter einen Block für die Benachrichtigung aufruft. Außerdem müssen Sie eine Operationqueue angeben, in der das Notificationcenter den Block ausführt.

- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
self.items = self.bookmarks;
[[NSNotificationCenter defaultCenter] addObserverForName:
MPMoviePlayerThumbnailImageRequestDidFinishNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *inNotification) {
NSDictionary *theInfo = inNotification.userInfo;
UIImage *theImage =
theInfo[MPMoviePlayerThumbnailImageKey];
NSTimeInterval theTime =
[theInfo[MPMoviePlayerThumbnailTimeKey]
doubleValue];

[self updateImage:theImage withTime:theTime];
}];
[self.collectionView reloadData];
}

Listing 9.104 Registrierung für die Thumbnail-Erzeugung

Da ein anderer Viewcontroller den Movieplayer verwaltet, sollte die Klasse BookmarksViewController nicht direkt auf den Movieplayer zugreifen. Deshalb implementiert die Klasse MoviePlayerViewController ein Delegateprotokoll des Bookmarks-Viewcontrollers.

@protocol BookmarksViewControllerDelegate<NSObject>

- (void)bookmarksViewController:(BookmarksViewController *)
inController needsImageAtTime:(NSTimeInterval)inTime;
- (void)bookmarksViewController:(BookmarksViewController *)
inController didUpdatePlaybackTime:(NSTimeInterval)inTime;

@end

Listing 9.105 Delegateprotokoll des Bookmarks-Viewcontroller

Über die Methode bookmarksViewController:needsImageAtTime: kann der Bookmarks-Viewcontroller die Erzeugung eines Vorschaubildes beim Delegate anfragen. Die zweite Methode, bookmarksViewController:didUpdatePlaybackTime:, dient dazu, die Abspielposition an den Movieplayer zu übergeben, wenn der Nutzer einen Eintrag aus den Lesezeichen ausgewählt hat.

- (UICollectionViewCell *)collectionView: 
(UICollectionView *)inCollectionView
cellForItemAtIndexPath:(NSIndexPath *)inIndexPath {
MovieCell *theCell = [inCollectionView
dequeueReusableCellWithReuseIdentifier:@"movie"
forIndexPath:inIndexPath];
NSTimeInterval theTime = [self.items[inIndexPath.row]
doubleValue];

theCell.text = [NSString stringWithFormat:
@"%d:%02d,%03ds", (int)(theTime / 60),
(int)theTime % 60, (int)(theTime * 1000) % 1000];
theCell.image = nil;
[self.delegate bookmarksViewController:self
needsImageAtTime:theTime];
return theCell;
}

Listing 9.106 Erzeugung der Zellen des Bookmarks-Viewcontrollers

Der Bookmarks-Viewcontroller, der ja auch als Datenquelle seines Collectionviews dient, kann ja über die erste Delegate-Methode die Erzeugung des Vorschaubildes anstoßen. Deswegen setzt die Methode collectionView:cellForItemAtIndexPath: aus Listing 9.106 das Bild der Zelle zunächst auf nil. Dieses Löschen hat in der Zelle die Nebenwirkung, eine Ladeanimation der Klasse UIActivityIndicatorView zu starten; dadurch bekommt der Nutzer eine Rückmeldung, dass die App das Bild noch erstellt. Der Setter der Klasse MovieCell weist also nicht nur das Bild zu, sondern steuert auch die Animation (siehe Listing 9.107).

- (void)setImage:(UIImage *)inImage {
self.imageView.image = inImage;
if(inImage == nil) {
[self.activityIndicatorView startAnimating];
}
else {
[self.activityIndicatorView stopAnimating];
}
}

Listing 9.107 Steuerung der Ladeanimation in den Zellen

Jetzt fehlt nur die Aktualisierung der Bilder in den Zellen durch die Benachrichtigung. Benachrichtigungen des Typs MPMoviePlayerThumbnailImageRequestDidFinishNotification liefert zwar den Zeitpunkt von der Stelle mit, an der der Film das gelieferte Bild enthält; dieser Zeitpunkt entspricht in der Regel jedoch nicht genau dem Zeitpunkt, für den Sie das Bild angefordert haben. Selbst mit der Option MPMovieTimeOptionExact kann die Differenz zwischen angefordertem und erhaltenem Zeitpunkt durchaus einige hundertstel Sekunden betragen.

Wenn der Bookmarks-Viewcontroller nun eine Benachrichtigung empfängt, muss er die die Position der Zelle ermitteln, zu der das Bild gehört. Dazu sucht die Methode nearestIndexForTime: die Position des Zeitpunktes aus den Bookmarks, der am nächsten zu dem übergebenen Zeitpunkt liegt. Sie bestimmt dazu von jedem Zeitpunkt in den Bookmarks die Differenz zum gesuchten Zeitpunkt, wobei die Funktion fabs sicherstellt, dass diese Werte immer positiv sind; dabei merkt sich die Methode in den Variablen theDelta und theNearestIndex jeweils die kleinste Differenz und die Position des entsprechenden Eintrags (siehe Listing 9.108).

- (NSUInteger)nearestIndexForTime:(NSTimeInterval)inTime {
double theDelta = MAXFLOAT;
NSUInteger theNearestIndex = NSNotFound;
NSUInteger theIndex = 0;

for(id theValue in self.bookmarks) {
double theCurrentDelta =
fabs(inTime – [theValue doubleValue]);

if(theCurrentDelta < theDelta) {
theDelta = theCurrentDelta;
theNearestIndex = theIndex;
}
++theIndex;
}
return theNearestIndex;
}

Listing 9.108 Suche der Position des nächstliegenden Bookmarks

Die Bilder in den Zellen lassen sich nun mit Hilfe der Methode cellForItemAtIndexPath: aktualisieren; diese Methode liefert entweder die Zelle zum angegebenen Indexpfad, falls der Collectionview diese Zelle gerade anzeigt, oder andernfalls nil. Den Code dieser Methode finden Sie in Listing 9.109.

- (void)updateImage:(UIImage *)inImage 
withTime:(NSTimeInterval)inTime {
NSUInteger theIndex = [self nearestIndexForTime:inTime];

if(theIndex != NSNotFound) {
NSIndexPath *thePath = [NSIndexPath
indexPathForRow:theIndex inSection:0];
MovieCell *theCell = (MovieCell *)[self.collectionView
cellForItemAtIndexPath:thePath];

theCell.image = inImage;
}
}

Listing 9.109 Bilder in den Bookmark-Zellen aktualisieren

Der Bookmarks-Viewcontroller zeigt damit nun die Vorschaubilder in den Zellen an. Dabei überbrückt er die Ladezeit durch eine passende Animation. Abbildung 9.21 stellt den View des Viewcontrollers dar, kurz nachdem ihn die App angezeigt hat.

Abbildung

Abbildung 9.21 Ladeanimationen der Bookmark-Zellen


Rheinwerk Computing - Zum Seitenanfang

9.4.4Videos über Layer anzeigenZur vorigen Überschrift

Obwohl der Movieplayer-Controller schon recht vielseitig ist, besitzt er auch einige Einschränkungen. Beispielsweise kann eine App damit immer nur einen Film gleichzeitig abspielen. Wenn Sie die Wiedergabe auf mehreren Controllern nacheinander starten, läuft immer nur der Film in dem zuletzt gestarteten Controller.

Das AVFoundation-Framework bietet mit der Klasse AVPlayerLayer eine weitere Möglichkeit, Filme anzuzeigen. Wie der Klassenname schon verrät, handelt es sich hierbei um keinen View, sondern um einen Layer. Das Beispielprojekt verwendet diese Layer für die Darstellung eines Intros, das den Film auf den Seitenflächen eines rotierenden Quaders darstellt (siehe Abbildung 9.22).

Abbildung

Abbildung 9.22 Intro für das Movieplayer-Beispielprojekt

Der Player-Layer dient ausschließlich zur Anzeige des Videos; das Abspielen und die Steuerung erfolgt dabei jeweils über ein Objekt der Klasse AVPlayer. Um einen Player-Layer mit einem laufenden Video anzuzeigen, müssen Sie zuerst einen Player über die Methode playerWithURL: und danach Player-Layer über die Methode mit dem schönen Namen playerLayerWithPlayer: erzeugen. Über die Methode play starten Sie schließlich die Wiedergabe. Diese Schritte gibt Listing 9.110 wieder.

NSURL *theURL = [[NSBundle mainBundle] 
URLForResource:@"elephants-dream" withExtension:@"mp4"];
AVPlayer *thePlayer = [AVPlayer playerWithURL:theURL];
AVPlayerLayer *theLayer =
[AVPlayerLayer playerLayerWithPlayer:thePlayer];

[thePlayer play];

Listing 9.110 Erzeugung eines Player-Layers

Das Intro verwendet für die Darstellung des Videoquaders insgesamt vier Player-Layer, die jeweils eine Drehung um 0°, 90°, 180° oder 270° um die y-Achse besitzen. Außerdem müssen Sie die Layer auf der z-Achse um die halbe Breite des Layers verschieben. Das geschieht mit Hilfe der Property anchorPointZ des Layers, die die Lage des Layers entlang der z-Achse beschreibt.

Zum besseren Verständnis sehen Sie in Abbildung 9.23 eine schematische Darstellung des Videoquaders von oben. Die entsprechenden Schritte dafür enthält Listing 9.111; dabei enthält die Konstante kCuboidSize die Breite und die Höhe des Quaders. Die Variable inStep gibt die Quaderseite des Layers an, und aus diesem Variablenwert berechnet der Code den Drehungswinkel des Layers um die y-Achse im Bogenmaß.

Abbildung

Abbildung 9.23 Die Geometrie des Quaders

Außerdem sollten Sie die doppelseitige Darstellung des Player-Layers über die Property doubleSided abschalten, um Fehler in der 3D-Darstellung zu vermeiden. Andernfalls können sich die Rückseiten der hinten liegenden Layer mit denen der weiter vorn liegenden überlagern.

AVPlayerLayer *theLayer = 
[AVPlayerLayer playerLayerWithPlayer:thePlayer];
CGRect theBounds = self.view.layer.bounds;
CGRect theFrame;

theFrame.origin = CGPointMake(

CGRectGetMidX(theBounds) kCubiodSize.width / 2.0,
CGRectGetMidY(theBounds) kCubiodSize.height / 2.0);
theFrame.size = kCubiodSize;
theLayer.frame = theFrame;
theLayer.anchorPointZ = kCubiodSize.width / 2.0;
theLayer.transform = CATransform3DMakeRotation(

inStep * M_PI / 2.0, 0.0, 1.0, 0.0);
theLayer.doubleSided = NO;

[thePlayer play];

Listing 9.111 Player-Layer im 3D-Raum positionieren

Damit nicht alle Layer den gleichen Inhalt anzeigen, spielen die Layer den Film von unterschiedlichen Startzeiten aus ab, wobei der Versatz jeweils 30 Sekunden zum Vorgänger ist. Der AVPlayer erhält dazu einen Wert der Struktur CMTime übergeben, die vier Attribute enthält. Das Attribut value enthält die Dauer und timescale dessen Skalierung, wobei beide Werte ganzzahlig sind. Die Zeit in Sekunden berechnet sich aus dem Verhältnis von value / timescale. Der Inhalt des Attributs flags ist für CMTime-Werte interessant, die Sie vom Player erhalten. Beim Setzen sollten Sie immer das Flag kCMTimeFlag_Valid setzen. Der Wert epoch beschreibt bei wiederholter Videowiedergabe die Anzahl der Wiederholungen; auch dieser Wert ist nur für zurückgegebene Werte interessant. Die Abspielposition lässt sich über die Methode seekToTime: des Players setzen, allerdings erst nach dem Start der Wiedergabe.

Listing 9.112 setzt auf die beschriebene Weise die Abspielposition des Players. Außerdem schaltet der Code über die Methode setVolume: die Lautstärke des Players aus, da die App die Anzeige des Quaders mit einem Musikstück akustisch hinterlegt.

CMTime theStartTime = {
inStep * 30, // value
1, // timescale
kCMTimeFlags_Valid, // flags
0 // epoch
};

[thePlayer play];
[thePlayer seekToTime:theStartTime];
[thePlayer setVolume:0.0];

Listing 9.112 Setzen des Startzeitpunktes und der Lautstärke

Den Code von Listing 9.110 bis Listing 9.112 finden Sie in der Methode makePlayerLayerWithStep: der Klasse IntroViewController. Mit Hilfe dieser Methode baut die Methode viewDidAppear: die Layerhierarchie für den Quader über eine Schleife zusammen. Für den 3D-Effekt setzt die Methode außerdem die Property sublayerTransform wie in Abschnitt 6.3.9, »Die 3. Dimension«, um eine Tiefenwirkung zu erzielen. Des Weiteren erzeugt die Methode die Animationen für den Quader. Dafür liegt es nahe, jeden Layer des Quaders einzeln zu animieren, was in der Praxis allerdings zu Darstellungsfehlern wie falschen Layerbewegungen und Layerüberlagerungen führt (siehe Abbildung 9.24).

Abbildung

Abbildung 9.24 Darstellungsfehler bei der 3D-Animation von Layern

Diese Probleme lassen durch einen Zwischenlayer beheben, der die vier Player-Layer des Quaders enthält. Die Animation bewegt dann den Zwischenlayer und somit die Layer des Quaders gemeinsam. Allerdings müssen Sie für den Zwischenlayer die Klasse CATransformLayer verwenden, der eine echte dreidimensionale Darstellung seiner Sublayer erlaubt. Andere Layer-Klassen stellen ihre Sublayer immer flach dar, unabhängig von deren Transformationsmatrix. Den Code zur Erzeugung des Quaders finden Sie in Listing 9.113.

CATransform3D theTransform = CATransform3DIdentity;
CALayer *theViewLayer = self.view.layer;
CALayer *theLayer = [CATransformLayer layer];

theTransform.m34 = 0.0005;
theViewLayer.sublayerTransform = theTransform;
theLayer.frame = theViewLayer.bounds;
[theViewLayer addSublayer:theLayer];
for(int i = 0; i < 4; i += 1) {
CALayer *theSublayer = [self makePlayerLayerWithStep:i];

[theLayer addSublayer:theSublayer];
}

Listing 9.113 Erzeugen des Videoquaders

Die Animation des Quaders setzt sich aus zwei Einzelanimationen zusammen: eine kontinuierliche Drehung um die z-Achse und ein Kippen um die y-Achse. Dabei hat die Rotation eine Umlaufzeit von 8 Sekunden, und die Kippanimation dauert nur 5 Sekunden, und die ungleichen Zeitspannen verstärken den Taumeleffekt der beiden Animationen. In Listing 9.114 steht der entsprechende Code.

CABasicAnimation *theAnimation = 
[CABasicAnimation animation];

theAnimation.toValue = @(2 * M_PI);
theAnimation.duration = 8.0;
theAnimation.repeatCount = MAXFLOAT;
theAnimation.autoreverses = NO;
[theLayer addAnimation:theAnimation
forKey:@"transform.rotation.y"];
theAnimation = [CABasicAnimation animation];
theAnimation.fromValue = @(-M_PI / 16.0);
theAnimation.toValue = @(M_PI / 16.0);
theAnimation.repeatCount = MAXFLOAT;
theAnimation.autoreverses = YES;
theAnimation.duration = 5.0;
[theLayer addAnimation:theAnimation
forKey:@"transform.rotation.x"];

Listing 9.114 Erzeugung der Animationen für den Quader

Wie Sie an diesem Beispiel sehen, können Sie Instanzen der Klasse AVPlayerLayer genauso flexibel und vielseitig wie andere Layerklassen einsetzen, und es ist damit auch möglich, beliebig viele Videos auf einmal anzuzeigen. Allerdings ist das Intro des Beispielprojekts natürlich sehr rechenintensiv und hat somit auch einen sehr hohen Energieverbrauch. Sie sollten also solche animierten Videodarstellungen mit Bedacht einsetzen und natürlich auch ausgiebig testen.



Ihr Kommentar

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

>> Zum Feedback-Formular
<< zurück




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


Nutzungsbestimmungen | Datenschutz | Impressum

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


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






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt bestellen


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

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






Einstieg in Objective-C 2.0 und Cocoa


Zum Katalog: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo