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.2Einbindung von HTML-DokumentenZur nächsten Überschrift

Über die Klassen NSAttributedString und NSMutableAttributedString ist es relativ einfach möglich, Texte mit Hervorhebungen darzustellen. Allerdings reichen diese beiden Klassen für komplexere Layouts nicht aus. So lassen sich beispielsweise damit keine Bilder darstellen. Das Vermischen von Texten und Bildern können Sie zwar prinzipiell durch Labels und Imageviews erreichen, was allerdings in der Regel sehr nervenaufreibend und fehlerträchtig ist. Das Umfließen von Bildern mit Text dürfte auf diesem Weg kaum zu realisieren sein. Solche Layouts lassen sich hingegen sehr gut über HTML beschreiben.

Die Abkürzung HTML steht für Hypertext Markup Language – HTML ist eine textbasierte Auszeichnungssprache. Mit einer Auszeichnungssprache lassen sich sowohl Inhalte (wie Texte und Bilder) als auch deren Darstellung (das Layout) beschreiben, wobei diese Beschreibung in einer einfachen Textdatei, einem HTML-Dokument, vorliegt. In der Regel sind ins HTML-Dokument eingebettete Bilder, Audiodateien, Videos usw. eigene Dateien, auf die das HTML-Dokument nur verweist. Diese Verweise bezeichnet man auch als Verlinkungen, und sie können relativ zum HTML-Dokument oder absolut über eine URL erfolgen.


Rheinwerk Computing - Zum Seitenanfang

9.2.1Anzeige von HTML-DokumentenZur nächsten ÜberschriftZur vorigen Überschrift

Zur Darstellung von HTML-Dokumenten in einer iOS-App dient die Klasse UIWebView. Sie können diese Views nach der Erzeugung über drei Wegen mit Inhalten befüllen, wozu es drei verschiedene Load-Methoden gibt.

Webviews können mehr

Die Klasse UIWebView ist die Basis für einen vollständigen Webbrowser in iOS-Applikationen, und sie kann neben HTML noch eine Reihe anderer Dokumenttypen anzeigen. Dazu gehören die folgenden Formate [Anm.: Siehe https://developer.apple.com/library/ios/#qa/qa2008/qa1630.html.] :

  • PDF
  • Keynote, Numbers und Pages
  • Excel, PowerPoint und Word
  • Rich Text Format

Außerdem ist über Webviews die direkte Anzeige von Audio-, Video- und Bilddokumenten möglich.

Wenn Sie eine Ressource über eine URL laden möchten, verwenden Sie in den meisten Fällen dafür die Methode loadRequest:, die einen Parameter vom Typ NSURLRequest erwartet.

- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
NSURL *theURL = ...;
NSURLRequest *theRequest =
[NSURLRequest requestWithURL:theURL];

[self.webView loadRequest:theRequest];
}

Listing 9.25 Webview mit den Daten einer URL-Anfrage füllen

Dafür können Sie natürlich auch URLs verwenden, die auf Dateien in der Sandbox Ihrer App verweisen. Wenn Sie beispielsweise eine HTML-Datei in den App-Ressourcen abgelegt haben, können Sie die entsprechende URL bequem über das Main-Bundle erzeugen. Beispielsweise erzeugen Sie die URL zu der Datei help.html im Ressourcenverzeichnis der App wie in Listing 9.26 erzeugen:

NSBundle *theMainBundle = [NSBundle mainBundle];
NSURL *theURL = [theMainBundle URLForRessource:help
withExtension:@"html"];

Listing 9.26 URL zu einer Datei in den App-Ressourcen erzeugen

Praktischerweise beachtet dieses Vorgehen auch eventuelle Lokalisierungen der Datei. Wenn Sie also über den Lokalisierungsmechanismus von Xcode (siehe Abschnitt 3.4.3, »Sprachkursus für die App«) entsprechende Varianten der Datei help.html angelegt haben, erzeugt Listing 9.26 je nach Sprachauswahl unterschiedliche URLs auf die entsprechenden Dateien in den lproj-Ordnern.

Projektinformation

Den Quellcode des Beispielprojekts WebView finden Sie auf der DVD unter Code/Apps/iOS6/WebView oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS6/WebView. Dieses Projekt illustriert die verschiedenen Möglichkeiten der Klasse UIWebView, die dieser Abschnitt vorstellt.

HTML-Inhalte können Sie auch dynamisch in einer Zeichenkette erzeugen und über die Methode loadHTMLString:baseURL: in den Webview laden. Dabei enthält der erste Parameter den HTML-Quelltext und der zweite die Basis-URL für relative Ressourcen im HTML-Dokument. Diese URL ist notwendig, wenn Sie beispielsweise Bilder oder Stylesheets nachladen möchten.

<html>
<head>
<link rel="stylesheet" type="text/css"
href="style.css" />
</head>
<body>
<p><img src="ball.png" />Hallo Welt</p>
</body>
</html>

Listing 9.27 HTML-Dokument mit relativer Stylesheet-Angabe

Wenn Sie beispielsweise das Dokument aus Listing 9.27 über die Methode loadHTMLString:baseURL: in einem Webview laden, dann muss der Webview wissen, wo er nach den Dateien style.css und ball.png suchen muss. Wenn Sie diese beiden Dateien im Ressourcen-Verzeichnis der App ablegen, können Sie als Basis-URL einfach die URL auf eine dieser Dateien verwenden. Der Code zum Laden könnte also so aussehen:

NSString *theContent = @"<html>...</html>";
NSURL *theURL = [[NSBundle mainBundle]
URLForResource:@"style" withExtension:@"css"];

[self.webView loadHTMLString:theContent baseURL:theURL];

Listing 9.28 Inhalt eines Webviews über einen String laden

Zum Laden von Bildern, PDF-Daten oder anderen Dokumenten können Sie die Methode loadData:MIMEType:textEncodingName:baseURL: verwenden. Deren erster Parameter ist ein Objekt der Klasse NSData, und der letzte Parameter ist eine Basis-URL, die der Webview für relativ verlinkte Ressourcen in den Daten benötigt. Der zweite Parameter ist eine Zeichenkette, die den Typ der Daten über deren MIME-Typ beschreibt, die die Internet Assigned Numbers Authority (IANA) festlegt. MIME-Typen bestehen dabei aus einem Haupt- und einem Untertyp. Der Haupttyp gibt die Art des Dokuments (z. B. Video, Bild) an, während der Untertyp in der Regel das konkrete Format (z. B. MP4, JPEG) beschreibt. In Tabelle 9.6 finden Sie einige häufig verwendete MIME-Typen [Anm.: Eine vollständige Liste aller von der IANA vergebenen MIME-Typen finden Sie unter: http://www.iana.org/assignments/media-types.] :

Tabelle 9.6 Beispiele für häufig verwendete MIME-Typen

MIME-Typ Inhalt

image/jpeg

JPEG-Bilder

image/gif

GIF-Bilder

image/png

PNG-Bilder

text/html

HTML-Dokumente

text/plain

einfache Textdateien ohne Formatierung

text/xml
application/xml

XML-Dokumente

application/pdf

PDF-Dokumente

application/msword

Word-Dateien

application/vnd.ms-excel

Excel-Spreadsheets

video/mp4

MP4-Videos

Über den dritten Parameter der Methode loadData:MIMEType:textEncodingName:baseURL: können Sie bei Textdaten die Zeichenkodierung des Textes angeben. Das sollten Sie auf jeden Fall für die MIME-Typen text/plain und text/html machen; bei XML-Dokumenten sollten Sie hingegen auf diese Angabe verzichten, da ja das Dokument bereits die Kodierung explizit oder implizit enthält.

Das Beispielprojekt WebView veranschaulicht die drei Methoden, mit denen Sie den Inhalt eines Webviews laden können. Es zeigt in einem Splitview eine Liste mit möglichen Inhalten an, die Sie in einen Webview laden können (siehe Abbildung 9.7). Die App enthält die Definitionen für die einzelnen Inhalte in einer Property-Liste, die sie aus der Datei pages.plist lädt.

Abbildung

Abbildung 9.7 Das Beispielprojekt »WebView« in Aktion

Das Laden erfolgt in der Methode loadContent: in der Klasse DetailViewController folgendermaßen:

- (void)loadContent:(NSDictionary *)inItem {
if(inItem[@"url"] != nil) {
NSURL *theURL =
[NSURL URLWithString:inItem[@"url"]];
NSURLRequest *theRequest =
[NSURLRequest requestWithURL:theURL];

[self.webView loadRequest:theRequest];
}
else if(inItem[@"content"] != nil) {
NSString *theContent = inItem[@"content"];
NSURL *theURL = [[NSBundle mainBundle] bundleURL];

[self.webView loadHTMLString:theContent
baseURL:theURL];
}
else {
NSURL *theURL = [[NSBundle mainBundle]
URLForResource:inItem[@"name"]
withExtension:inItem[@"extension"]];
NSData *theData =
[NSData dataWithContentsOfURL:theURL];

[self.webView loadData:theData
MIMEType:inItem[@"contentType"]
textEncodingName:inItem[@"encoding"]
baseURL:theURL];
}
[self.activityIndicator startAnimating];
}

Listing 9.29 Varianten zum Laden von Webview-Inhalten

Die Methode unterscheidet anhand der vorhandenen Schlüssel in dem übergebenen Dictionary, welche Methode sie zum Laden verwendet. Wenn es den Schlüssel »url« enthält, lädt sie den Inhalt über eine URL-Anfrage. Die Methode wandelt den Wert zu diesem Schlüssel in eine URL um und erzeugt damit ein Objekt der Klasse NSURLRequest.

Wenn das Dictionary stattdessen den Schlüssel »content« enthält, liest die Methode den Wert dieses Eintrags aus und lädt diese Zeichenkette in den Webview über einen Aufruf der Methode loadHTMLString:baseURL:. Die dritte Variante erzeugt schließlich aus den Werten zu den Schlüsseln »name« und »extension« eine URL im Ressourcenverzeichnis der App und lädt über diese URL die Daten der Ressource. Das Laden erfolgt dann über die Methode loadData:MIMEType:textEncodingName:baseURL:.


Rheinwerk Computing - Zum Seitenanfang

9.2.2Javascript-Dateien einbindenZur nächsten ÜberschriftZur vorigen Überschrift

Sie können die Ressourcen einer HTML-Seite aus dem Ressourcenverzeichnis der App einlesen, wenn Sie die Basis-URL entsprechend auf dieses Verzeichnis setzen. Das funktioniert auch bei Stylesheet-, Audiodateien, Filmen und vielen anderen Formaten problemlos. Bei Javascript-Dateien ist das allerdings nicht ganz so einfach. Zwar erkennt Xcode die Dateien an der Dateiendung als Javascript-Dateien, weiß aber nicht so recht etwas damit anzufangen und gibt stattdessen – zumindest in den Versionen 4.x – eine Fehlermeldung aus (siehe Abbildung 9.8).

Abbildung

Abbildung 9.8 Fehlermeldung beim Einbinden von Javascript-Dateien

Sie müssen also Xcode erst mitteilen, was Sie mit dieser Datei vorhaben. Das ist in diesem Fall einfach: Xcode soll sie beim Erstellen der App in das Ressourcenverzeichnis der App kopieren. Dazu ziehen Sie die Datei einfach per Drag & Drop aus dem Datei-Navigator in Copy Bundle Resources, wie Abbildung 9.9 es zeigt.

Abbildung

Abbildung 9.9 Javascript-Datei in die richtige Build-Phase schieben

Javascript-Dateien in der Compile-Sources-Phase

Unter Umständen hat Xcode die Javascript-Datei auch der Compile-Sources-Phase hinzugefügt. Das sollten Sie prüfen und gegebenenfalls die Datei aus dieser Phase löschen.

Durch diese Änderung kopiert Xcode nun tatsächlich die Datei in das Ressourcenverzeichnis der App. Sie können diese Datei jetzt in Ihre HTML-Seiten aus dem Ressourcenverzeichnis einbinden, indem Sie die URL auf dieses Verzeichnis als Basis-URL verwenden (siehe Listing 9.29). In der HTML-Seite können Sie dann auf die Javascript-Datei über eine relative Verlinkung zugreifen.

<html>
<head>
<script type="text/javascript" src="script.js">
</script>
</head>
<body>
</body>
</html>

Listing 9.30 Javascript-Datei in HTML-Dokument einbinden

Strenger Webview

Listing 9.30 verwendet für das script-Tag die lange Form mit öffnendem und schließendem Tag. Die Kurzform

<script type="text/javascript" src="script.js" />

die diese beiden Tags zusammenfasst, führt zu einem Fehler und dadurch zu einer leeren Seite. Sie sollten also immer die lange Form verwenden.


Rheinwerk Computing - Zum Seitenanfang

9.2.3Das Delegate des WebviewsZur nächsten ÜberschriftZur vorigen Überschrift

Die Methode loadContent: aus Listing 9.29 startet am Ende noch eine Aktivitätsanzeige, um den Ladevorgang anzuzeigen. Nach Beendigung muss die App diese Animation noch stoppen. Dazu kann sie die Delegate-Methoden webViewDidFinishLoad: und webView:didFailLoadWithError: implementieren, die der Webview jeweils nach dem erfolgreichen Laden beziehungsweise beim Auftreten eines Ladefehlers aufruft. Die Methode webView:didFailLoadWithError: können Sie außerdem dazu verwenden, den Nutzer auf den Ladefehler hinzuweisen. Das Beispielprojekt verwendet dazu einfach einen Alertview, der die Fehlermeldung anzeigt:

- (void)webViewDidFinishLoad:(UIWebView *)inWebView {
[self.activityIndicator stopAnimating];
}

- (void)webView:(UIWebView *)inWebView
didFailLoadWithError:(NSError *)inError {
UIAlertView *theAlert = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(@"Error", @"")
message:inError.localizedDescription
delegate:nil cancelButtonTitle:@"OK"
otherButtonTitles:nil];

[self.activityIndicator stopAnimating];
[theAlert show];
}

Listing 9.31 Beendigung des Ladevorgangs verarbeiten

Wenn Sie in einer der geladenen Webseiten allerdings auf einen Link klicken, startet die App nicht die Aktivitätsanzeige. Das ist auch nicht weiter verwunderlich, da der Webview ja die Anzeige und ihren Zweck nicht kennt. Über die Delegate-Methode webViewDidStartLoad: können Sie diesen Mangel jedoch recht leicht beheben.

- (void)webViewDidStartLoad:(UIWebView *)inWebView {
if(![self.activityIndicator isAnimating]) {
[self.activityIndicator startAnimating];
}
}

Listing 9.32 Starten der Aktivitätsanzeige beim Laden neuer Inhalte

Netzzugriffe visualisieren

Zusätzlich oder anstatt der Aktivitätsanzeige auf dem Webview können Sie auch in der Statusleiste die Aktivitätsanzeige für Netzzugriffe verwenden. Sie können diese Animation über die Property networkActivityIndicatorVisible des Application-Singletons steuern, also beispielsweise über

[[UIApplication sharedApplication] 
setNetworkActivityIndicatorVisible:YES];

starten beziehungsweise über

[[UIApplication sharedApplication] 
setNetworkActivityIndicatorVisible:NO];

stoppen.

Über die Delegate-Methode webView:shouldStartLoadWithRequest:navigationType: können Sie verhindern, dass der Webview bestimmte URLs lädt. Das Beispielprojekt unterdrückt beispielsweise das Laden von Seiten, deren URL-Pfade die Bestandteile .gz oder .zip enthalten.

- (BOOL)webView:(UIWebView *)inWebView 
shouldStartLoadWithRequest:(NSURLRequest *)inRequest
navigationType:(UIWebViewNavigationType)inType {
NSString *thePath = [inRequest.URL path];

return [thePath rangeOfString:@".gz"].location ==
NSNotFound &&
[thePath rangeOfString:@".zip"].location ==
NSNotFound;
}

Listing 9.33 Laden bestimmter URLs verhindern

Die Aufrufreihenfolge der vier Delegate-Methoden stellt Abbildung 9.10 dar. Dabei fällt auf, dass der Webview die Methode webView:shouldStartLoadWithRequest:navigationType: durchaus mehrmals aufrufen kann. Der erste Aufruf erfolgt unmittelbar zu Beginn nach Aufruf einer Lademethode des Webviews. Sofern dieser Aufruf das Ergebnis YES liefert, beginnt der Webview mit dem Datenzugriff und ruft die Delegate-Methode webViewDidStartLoad: auf.

Abbildung

Abbildung 9.10 Aufrufreihenfolge der Delegate-Methode

Der Webserver kann auf die Anfrage nicht nur mit den Daten für eine Webseite oder mit einem Fehler, sondern auch mit einer Anfrageweiterleitung antworten. Dabei enthält die Antwort einen speziellen Header, der den Client zu einer weiteren Anfrage auffordert. Wenn Sie beispielsweise die Daten für die URL http://www.apple.de anfragen, erhalten Sie eine Antwort mit dem Header aus Listing 9.34 vom Server. Dabei gibt die Zeile mit dem Schlüssel Location: das Ziel der Weiterleitung an.

HTTP/1.1 301 Moved Permanently
Server: somethingNice.
Date: Tue Jun 1 12:48:03 PDT 1999 PDT
Referer: http://apple.com/
Location: http://www.apple.com/de/

Content-type: text/html
Content-length: 291

Listing 9.34 Header einer Anfrageweiterleitung

Wenn der Webview eine Anfrageweiterleitung als Antwort erhält, fragt er erneut sein Delegate über die Methode webView:shouldStartLoadWithRequest:navigationType:, ob er die Daten für die URL der Weiterleitung laden soll. Wenn die Delegate-Methode mit YES antwortet, geht das Spielchen wieder von vorn los; d. h., der Webserver kann wieder mit einer Seite, einem Fehler oder einer Anfrageweiterleitung antworten. Aus einer Anfrage können also beliebig viele weitere Anfragen entstehen, wobei allerdings der Webview für jede Weiterleitung das Delegate fragt, ob es die Weiterleitung zulässt.

Das kann aber doch nicht ewig so weitergehen

Der Webview führt Weiterleitungen beliebig oft aus, sofern es die Delegate-Methode webView:shouldStartLoadWithRequest:navigationType: erlaubt. Wenn Sie diese Methode nicht implementieren, interpretiert es der Webview als Zustimmung. Solche Endlosweiterleitungen können erwünscht (z. B. Fortschrittsanzeigen, Lauftexte auf HTML-Basis) oder auch ein Fehler des Webseitenbetreibers sein. Wie Sie damit umgehen, entscheiden Sie über die Delegate-Methode.

Freigabe des Webviews

Die Apple-Dokumentation empfiehlt, das Delegate vor der Freigabe des Webviews auf nil zu setzen. Anscheinend kann der Webview auch nach der Freigabe noch Delegatenachrichten senden; wenn das Delegateobjekt dann jedoch nicht mehr existiert, kann das zu Abstürzen durch einen Dangling Pointer führen.

Diese Situation kann beispielsweise dann eintreten, wenn die App den Viewcontroller des Webviews freigibt und seine dealloc-Methode aufruft. Dann hat zwar der Viewcontroller den Webview über release freigegeben, es kann dennoch andere haltende Referenzen auf den Webview geben, die dessen Zerstörung verzögern. Wenn der Webview nun eine Nachricht an das nicht mehr existierende Delegateobjekt sendet, kann das zu einem Absturz führen.

Um den Dangling Pointer zu vermeiden, reicht es aus, das Delegate in der dealloc-Methode des Viewcontrollers auf nil zu setzen, wie Listing 9.35 zeigt:

- (void)dealloc {
self.webView.delegate = nil;
}

Listing 9.35 Delegate des Webviews auf »nil« setzen


Rheinwerk Computing - Zum Seitenanfang

9.2.4Webviews und ScrollviewsZur nächsten ÜberschriftZur vorigen Überschrift

Ein Webview enthält mehrere Subviews, zu denen unter anderem ein Scrollview gehört. Dieser Scrollview ist allerdings der einzige Subview des Webviews, auf den Sie seit iOS 5 zugreifen dürfen; dafür gibt es die Property scrollView. Über den Scrollview können Sie den anzuzeigenden Ausschnitt und die Skalierung des Inhalts festlegen. Das Beispielprojekt zeigt unter dem Webview drei Buttons in der Toolbar an, mit denen Sie den aktuellen Ausschnitt des Webviews auf jeweils feste Positionen festlegen können. Am einfachsten geht das für die Position top, also die obere linke Ecke. Dazu setzen Sie den Wert der Property contentOffset auf die Koordinate (0, 0).

- (IBAction)scrollToTop {
[self.webView.scrollView setContentOffset:CGPointZero
animated:YES];
}

Listing 9.36 Ausrichtung des Webviews an der oberen linken Ecke

Für die Zentrierung und die Ausrichtung an der unteren rechten Ecke müssen Sie den entsprechenden Offset jeweils aus der Differenz der Gesamtgröße des Inhalts (contentSize) und der sichtbaren Größe (bounds.size) des Scrollviews berechnen.

- (IBAction)scrollToCenter {
UIScrollView *theScrollView = self.webView.scrollView;
CGSize theContentSize = theScrollView.contentSize;
CGSize theViewSize = theScrollView.bounds.size;
CGPoint theOffset = CGPointMake(
fmaxf(theContentSize.width – theViewSize.width, 0.0) / 2.0,
fmaxf(theContentSize.height – theViewSize.height, 0.0) / 2.0);

[theScrollView setContentOffset:theOffset animated:YES];
}

- (IBAction)scrollToBottom {
UIScrollView *theScrollView = self.webView.scrollView;
CGSize theContentSize = theScrollView.contentSize;
CGSize theViewSize = theScrollView.bounds.size;
CGPoint theOffset = CGPointMake(
fmaxf(theContentSize.width – theViewSize.width, 0.0),
fmaxf(theContentSize.height – theViewSize.height, 0.0));

[theScrollView setContentOffset:theOffset animated:YES];
}

Listing 9.37 Zentrierung und Ausrichtung nach der rechten unteren Ecke

Während sich der Inhaltsausschnitt des Webviews über dessen Scrollview recht gut auswählen lässt, schränkt der Webview die Möglichkeiten für die Skalierung – zumindest bis iOS 6 – ein. So lassen sich beispielsweise die Dokumente in der Regel nur vergrößern, und Skalierungswerte kleiner als eins ignoriert der Webview beziehungsweise sein Scrollview.

Das Beispielprojekt WebView enthält in der Navigationsleiste einen Schieberegler, mit dem Sie die Skalierung des Webviews regeln können. Da der Webview den Skalierungsbereich seines Scrollviews in Abhängigkeit von dem Inhalt setzt, überträgt die Delegate-Methode webViewDidFinishLoad: die Werte des Skalierungsbereiches auf den Schieberegler. Dadurch können Sie über den Schieberegler nur zulässige Werte einstellen:

UIScrollView *theScrollView = self.webView.scrollView;

self.slider.minimumValue = theScrollView.minimumZoomScale;
self.slider.maximumValue = theScrollView.maximumZoomScale;
self.slider.value = theScrollView.zoomScale;

Listing 9.38 Übertragung des Skalierungsbereiches

Allerdings stellen Sie anhand des Beispielprojekts ein ungewöhnliches Verhalten des Webviews fest: Sie können zwar über den Schieberegler den Inhalt vergrößern, eine anschließende Verkleinerung funktioniert jedoch nicht oder allenfalls sehr eingeschränkt.

Die Klasse UIWebView unterstützt außerdem ein Standardverhalten für den Scrollview. Dazu gehören die Anpassung der Skalierung über Pinchgesten und schnelles Vergrößern und Verkleinern des Inhalts über Double-Taps. Sie können dieses Verhalten über die Property scalesPageToFit steuern. Wenn Sie der Property den Wert YES zuweisen, schalten Sie die Erkennung der Pinch- und Double-Tap-Gesten ein, und außerdem skaliert der Webview den Inhalt nach dem Laden, so dass dieser in den Webview passt.


Rheinwerk Computing - Zum Seitenanfang

9.2.5Der ViewportZur nächsten ÜberschriftZur vorigen Überschrift

In diesem Abschnitt erläutern wir einige Möglichkeiten, wie Sie Ihre HTML-Seiten für die Anzeige in einem Webview verbessern können. Der Viewport beschreibt die Anzeigebereich einer Webseite, und sie können seine Parameter über ein HTML-Metatag beschreiben. Sie legen die Parameter für den Viewport innerhalb head-Tags der Webseite über ein meta-Tag wie in Listing 9.39 fest.

<head>
...
<meta name="viewport" content="...">
...
</head>

Listing 9.39 Angabe des Viewports

Dabei enthält das content-Attribut die Werte als eine Liste von Schlüssel-Wert-Paaren. Tabelle 9.7 gibt Ihnen eine Übersicht mit den möglichen Parametern für den Viewport.

Tabelle 9.7 Parameter für den Viewport

Schlüssel Beschreibung Wertebereich

width

Die Breite des Viewports in Pixeln.
Der Standardwert ist 980.

200 bis 10000,
device-width

height

die Höhe des Viewports in Pixel

200 bis 10000,
device-height

initial-scale

die Skalierung des Inhalts im Viewport

Werte zwischen minimum-scale und maximum-scale

minimum-scale

Legt die minimale Skalierung fest. Der Standardwert ist 0.25.

0.0 (exklusive) bis 10.0 (inklusive)

maximum-scale

Legt die maximale Skalierung fest. Der Standardwert ist 5.0.

user-scalable

Schaltet die Zoomfunktion des Nutzers aus oder ein.

no, yes

Sie können mehrere Parameter, durch Kommas getrennt, in einer Viewport-Angabe setzen. Wenn Sie die Breite und den Skalierungsfaktor setzen möchten, kann das beispielsweise so aussehen:

<meta name="viewport" content="width=480, initial-scale=1.5">

Wenn Sie keinen Wert für width angeben, verwendet der Webview den Standardwert von 980 Pixeln. Der Webview zeichnet damit also den Inhalt auf eine 980 Pixel breite Zeichenfläche, was ja durchaus ein realistischer Wert für ein Browserfenster auf einem Desktoprechner ist. Diesen Inhalt verkleinert er in der Regel auf die tatsächliche Breite des Webviews; meistens also auf die Breite des iPads oder iPhones.

Auf die Größe kommt es an

Die Angaben für width und height legen die Größe des Viewports in Pixeln fest. Diese Größe sagt aber nichts über die Anzeigegröße aus, sondern legt die Größe der HTML-Seite fest, die der Webview dann gegebenenfalls noch so skaliert, dass sie komplett in den Webview passt. In der Regel geben Sie nur die Breite vor. Den Standardwert für die Höhe berechnet der Webview automatisch, so dass keine Verzerrung bei der Darstellung entsteht.

Bei einem iPad sollte diese Verkleinerung die Lesbarkeit des Inhalts nicht stark einschränken. Auf einem iPhone hingegen schrumpft die Breite auf ca. ein Drittel zusammen, was die Lesbarkeit von Texten häufig sehr erschwert. Natürlich kann der Nutzer den Inhalt wieder vergrößern, so dass er besser lesbar ist. Das ist jedoch unpraktisch, da nach dem Laden jeder neuen Seite der Nutzer zunächst zoomen und außerdem dann sowohl horizontal als auch vertikal scrollen muss.

Diese beiden Nachteile lassen sich vermeiden, indem Sie die Breite des Viewports von vornherein beschränken und dadurch den Webview zwingen, die Elemente in der HTML-Seite anders anzuordnen. Wenn Sie nämlich eine kleinere Breite vorgeben, fügt der Webview in der Regel mehr Zeilenumbrüche ein. Abbildung 9.11 veranschaulicht das schematisch. Die linke Seite stellt oben einen breiten Viewport dar, der zwar den kompletten Text anzeigt. Für die Darstellung auf dem iPhone muss der Webview diesen Viewport jedoch stark verkleinern, was die Lesbarkeit des Textes erschwert (unten).

Bei einem schmaleren Viewport (in der Abbildung rechts oben) fügt der Webview hingegen weitere Zeilenumbrüche ein. Dadurch muss der Webview den Inhalt nicht skalieren und stellt den Text in einer lesbaren Schriftgröße dar.

Eine andere Anordnung der Inhaltselemente ist allerdings nicht nur bei Fließtext, sondern auch bei anderen HTML-Elementen möglich, wenn die Seite ein entsprechend flexibles Layout besitzt. Ein Beispiel dafür sehen Sie in Abbildung 9.12. Der Webview ordnet bei einem breiten Viewport die Bilder und den Text nebeneinander an und bei einem schmalen untereinander.

Abbildung

Abbildung 9.11 Auswirkung unterschiedlicher Viewport-Größen

Abbildung

Abbildung 9.12 Flexible Anordnung der HTML-Elemente

Flexibles HTML

Damit sich die Seiteninhalte flexibel an die Breite des Viewports anpassen können, sollten Sie möglichst keine Tabellen oder absolute Positionierung für die HTML-Elemente verwenden. Bevorzugen Sie stattdessen lieber fließende Layouts, beispielsweise über die CSS-Eigenschaft float.

Eine sehr gute Lesbarkeit des Inhalts erreichen Sie in der Regel, wenn die Breite des Viewports der tatsächlichen Breite des Webviews entspricht. Wenn Sie eine Seite sowohl auf dem iPad als auch auf dem iPhone darstellen wollen, können Sie dafür jedoch keinen festen Wert angeben. Hierfür ist die spezielle Angabe device-width gedacht. Damit verwendet der Webview immer die Breite des Webviews als Breite für den Viewport.


Rheinwerk Computing - Zum Seitenanfang

9.2.6Dynamische HTML-SeitenZur nächsten ÜberschriftZur vorigen Überschrift

Sie können nun beliebige HTML-Seiten in einem Webview anzeigen. Falls die App das HTML-Dokument erst zur Laufzeit erzeugen soll, können Sie natürlich eine entsprechende Zeichenkette sukzessive aufbauen, indem Sie die statischen Teile der Seite mit den dynamischen verknüpfen. Dieses Vorgehen entpuppt sich jedoch schnell als sehr nervenaufreibend, da man leicht den Überblick verliert.

Viel besser ist es, die statischen Teile der Seite aus einer Datei zu laden und die variablen Teile dort einzufügen. In Cocoa Touch bietet sich an, das mit Key-Value-Coding umzusetzen. Dabei klammern Sie jeden Ersetzungsschlüssel im Text in jeweils eine Start- und Endmarkierung; das Beispielprojekt verwendet hierfür ${ und }$. Sie können beispielsweise ${name}$ in der Datei schreiben, um den Wert zu dem Schlüssel name in die HTML-Seite einzufügen. Das Beispielprojekt WebView enthält die Datei hello.tmpl, die als Vorlage für HTML-Seiten dient:

<html>
<head>
</head>
<body>
<h1>${title}$</h1>
<p style="text-align: right;">${city}$, ${date}$</p>
<h3>Hallo ${name}$,</h3>
<p>${text}$</p>
</body>
</html>

Listing 9.40 HTML-Vorlage mit Platzhaltern

Diese Vorlage muss die DetailViewController jetzt laden und darin alle Platzhalter durch die entsprechenden Werte ersetzen. Die Platzhalter in dem Text lassen sich recht einfach über einen NSScanner aufspüren. Die Methode stringWithValuesOfObject: in der Kategorie NSString(Template) verwendet einen Scanner, um jeweils die Start- und Endmarkierungen in dem Platzhaltertext zu finden. Dabei benutzt sie den Text zwischen den Markierungen als Schlüssel, um über die Methode valueForKey: den dazugehörenden Wert aus dem Parameterobjekt zu ermitteln:

static NSString * const kStartToken = @"${";
static NSString * const kEndToken = @"}$";

- (NSString *)stringWithValuesOfObject:(id)inObject {
NSScanner *theScanner =
[NSScanner scannerWithString:self];
NSMutableString *theResult = [NSMutableString string];
NSString *theString = nil;

if([theScanner scanUpToString:kStartToken
intoString:&theString]) {
[theResult appendString:theString];
}
while([theScanner scanString:kStartToken
intoString:NULL]) {
if([theScanner scanUpToString:kEndToken
intoString:&theString])
if([theScanner scanString:kEndToken
intoString:NULL]) {
id theValue =
[inObject valueForKey:theString];

theString = theValue == nil ? @"" :
[theValue description];
}
[theResult appendString:theString];
}
if([theScanner scanUpToString:kStartToken
intoString:&theString]) {
[theResult appendString:theString];
}
}
return [theResult copy];
}

Listing 9.41 Platzhalter in einer HTML-Vorlage ersetzen

Die Methode aus Listing 9.41 sucht zunächst die Startmarkierung des ersten Platzhalters und hängt die bis dahin eingelesene Zeichenkette in der Variablen theString an das Ergebnis theResult an. Dabei liefert die Methode scanUpToString:intoString: nur dann YES, wenn der Scanner auch tatsächlich eine Zeichenkette in den Ausgabeparameter theString eingelesen hat.

Ein Nein ist ein Nein

Falls die Methode scanUpToString:intoString: das Ergebnis NO liefert, hat sie die Ausgabeparameter nicht verändert. Da Listing 9.41 die Variable theString vorher jedoch nicht initialisiert hat, ist ihr Wert dann nach dem Methodenaufruf immer noch undefiniert, und eine Verwendung dieser Zeichenkette führt höchstwahrscheinlich zu einem Programmabsturz.

Die Schleife überliest als Erstes die Startmarkierung mittels der Methode scanString:intoString: und liest danach den Schlüssel über scanUpToString:intoString: in die Variable theString ein. Die Methode verwendet diesen Wert aber nur, wenn sie auch die Endmarkierung dazu findet, was sie über einen Aufruf der Methode scanString:intoString: nachprüft.

Die Methode ermittelt nun über valueForKey: den Wert zu diesem Schlüssel und hängt ihn an das Ergebnis an. Damit danach die Schleife wieder von vorn beginnen kann, sucht die Methode die nächste Startmarkierung über die Methode scanString:intoString:. Listing 9.42 zeigt die Änderungen für die Methode loadContent: aus Listing 9.29; fügen Sie diese Zeilen einfach vor dem letzten else-Block ein.

else if(inItem[@"template"] != nil) {
NSString *theData =
[self stringForResource:inItem[@"template"]
withExtension:@"tmpl"];
NSURL *theURL = [[NSBundle mainBundle] bundleURL];

theData = [theData stringWithValuesOfObject:self];
[self.webView loadHTMLString:theData baseURL:theURL];
}

Listing 9.42 Einbindung von Vorlagen ins Beispielprojekt

Dabei lädt die Methode stringForResource:withExtension: (siehe Listing 9.43) eine Zeichenkette aus einer beliebigen Datei, die sich im Ressourcenverzeichnis der App befindet. Sie verwendet dazu die Methode stringWithContentsOfURL:usedEncoding:error:, die Sie bereits in Abschnitt 8.4.1, »Synchrone Kommunikation«, kennengelernt haben.

- (NSString *)stringForResource:(NSString *)inResource 
withExtension:(NSString *)inExtension {
NSURL *theURL =
[[NSBundle mainBundle] URLForResource:inResource
withExtension:inExtension];
NSError *theError = nil;
NSString *theContent =
[NSString stringWithContentsOfURL:theURL
usedEncoding:NULL error:&theError];

if(theContent == nil) {
NSLog(@"loadStringForResource:%@ withExtension:%@:
url=%@, error=%@", inResource, inExtension,
theURL, theError);
}
return theContent;
}

Listing 9.43 Zeichenkette aus dem Ressourcenverzeichnis laden

Die App stürzt allerdings mit einer NSUnknownKeyException ab, da die Klasse DetailViewController ja die in der Vorlage verwendeten Schlüssel nicht kennt. Um dieses Problem zu beheben, können Sie für jeden Schlüssel einfach eine entsprechende Getter-Methode anlegen. Das Beispielprojekt legt die Werte der Schlüssel in den User-Defaults ab; also könnte beispielsweise für title der Getter so aussehen:

- (NSString *)title {
NSUserDefaults *theDefaults =
[NSUserDefaults standardUserDefaults];

return [theDefaults objectForKey:@"title"];
}

Listing 9.44 Getter-Methode für einen Schlüssel der Vorlage

Bei wenigen Schlüsseln mag das ja noch ein praktikables Vorgehen zu sein, bei sehr vielen Schlüsseln erhalten Sie jedoch sehr schnell eine unübersichtliche Klasse. Andererseits ist es natürlich sehr praktisch, wenn die App für die Erzeugung des Wertes eine eigene Methode aufruft. Mit Key-Value-Coding haben Sie jedoch die Möglichkeit, auch einen generellen Ansatz zu wählen, den Sie im Bedarfsfall individuell anpassen. Dazu überschreiben Sie die Methode valueForUndefinedKey:, die KVC aufruft, wenn es einen Schlüssel nicht findet:

- (id)valueForUndefinedKey:(NSString *)inKey {
NSUserDefaults *theDefaults =
[NSUserDefaults standardUserDefaults];
id theValue = [theDefaults objectForKey:inKey];

return theValue == nil ? @"" : [theValue description];
}

Listing 9.45 Allgemeine Bestimmung eines Schlüsselwertes

Durch diese Methode zeigt das Beispielprogramm nun auch eine Seite im Webview an, wenn Sie den Menüpunkt Template auswählen. Wenn Sie nun den Wert zu einem Schlüssel lieber berechnen wollen, schreiben Sie dafür einfach einen Getter. Beispielsweise soll der Controller für den Schlüssel »date« immer das aktuelle Datum als Text liefern. Der entsprechende Getter sieht dafür so aus:

- (NSString *)date {
NSDateFormatter *theFormatter =
[[NSDateFormatter alloc] init];
NSString *theDate;

theFormatter.locale = [NSLocale currentLocale];
theFormatter.dateStyle = NSDateFormatterMediumStyle;
theFormatter.timeStyle = NSDateFormatterNoStyle;
theDate = [theFormatter stringFromDate:[NSDate date]];
return [theDate stringByEscapingXMLSpecialCharacters];
}

Listing 9.46 Ausgabe des Datums über einen Getter

Wenn der Controller nun die Vorlage aus Listing 9.40 verarbeitet, ruft die Methode valueForKey: für den Schlüssel »title« die Methode valueForUndefinedKey: mit diesem Schlüssel auf. Der Schlüssel »date« führt hingegen zum Aufruf der Methode date. Sie können übrigens die Werte für die Schlüssel »title«, »name«, »city« und »text« über den Button Data in der App ändern. Dieser Button öffnet ein Pop-over mit einem Einstellungsdialog, der auf einem statischen Tableview beruht, analog zum Einstellungsdialog des Weckers aus Abschnitt 4.3, »Navigation- und Pop-over-Controller in der Praxis«.


Rheinwerk Computing - Zum Seitenanfang

9.2.7HTML-Sonderzeichen maskierenZur nächsten ÜberschriftZur vorigen Überschrift

Sie können für die Werte im Datendialog beliebige Texte eingeben, die die App genau so in die Vorlage einfügt. Sie können hier beispielsweise auch HTML-Tags eingeben, die der Webview dann auch wie gewöhnlichen HTML-Quelltext interpretiert. Wenn Sie beispielsweise »<u>Udo Unterstrich</u>« eingeben, zeigt Ihnen die App diesen Text unterstrichen an, also als Udo Unterstrich. Das ist häufig nicht gewünscht; insbesondere nicht, wenn die Werte durch Texteingaben des Nutzers entstehen. Das kann zu Sicherheitslücken (HTML-Injection) oder Programmabstürzen führen.

Dieses Problem lässt sich jedoch leicht umgehen, indem Sie die HTML-Sonderzeichen in den Werten maskieren. Hierbei reicht es im Allgemeinen aus, die XML-Sonderzeichen durch die entsprechende XML-Entität (siehe Abschnitt 8.3.1, »XML in Kürze«) zu ersetzen.

- (NSString *)stringByEscapingSpecialXMLCharacters {
NSMutableString *theResult = [NSMutableString string];
NSUInteger theLength = [self length];

for(NSUInteger i = 0; i < theLength; ++i) {
unichar theCharacter = [self characterAtIndex:i];

switch (theCharacter) {
case '&':
[theResult appendString:@"&amp;"];
break;
case '<':
[theResult appendString:@"&lt;"];
break;
case '>':
[theResult appendString:@"&gt;"];
break;
case '\'':
[theResult appendString:@"&apos;"];
break;
case '"':
[theResult appendString:@"&quot;"];
break;
default:
[theResult appendFormat:@"%C",
theCharacter];
break;
}
}
return [theResult copy];
}

Listing 9.47 HTML-Sonderzeichen ersetzen

Die Methode stringByEscapingSpecialXMLCharacters, die sich in der Kategorie NSString(Template) befindet, geht die Zeichenkette zeichenweise durch und ersetzt über eine switch-Anweisung die Sonderzeichen durch ihre jeweiligen XML-Entitäten; alle anderen Zeichen fügt sie unverändert im Default-Teil an das Ergebnis an.

Mit dieser Kategoriemethode können Sie nun die App gegen HTML-Injection schützen, indem Sie die Methoden date und valueForUndefinedKey: entsprechend anpassen. Sie brauchen in diesen Methoden jeweils nur an den Rückgabewert die Nachricht stringByEscapingSpecialXMLCharacters zu senden.

Hier stinkt es: Switches und If-Ketten

Fallunterscheidungen durch Switch-Anweisungen und If-Ketten gelten in der objektorientierten Programmierung zu Recht als Code-Smell, wenn sie anstelle von objektorientierten Konzepten (z. B. Vererbung) eingesetzt werden. Ein typisches Beispiel für so einen Stinker ist es, Objektzustände über Integer-Variablen abzubilden und das zustandsabhängige Verhalten über solche Fallunterscheidungen zu implementieren. Diese Fallunterscheidungen beginnen leicht zu streuen; d. h., sie finden sich an ganz vielen Stellen im Code wieder. Dadurch verschlechtert sich die Wartbarkeit des Codes zunehmend, weil Sie bei jeder Änderung an der Menge der möglichen Zustände alle Fallunterscheidungen anpassen müssen. Es ist in der Regel vorteilhafter, die Zustände über eine eigene Klassenhierarchie zu definieren, dann lässt sich die Fallunterscheidung durch das Überschreiben von Methoden realisieren.

Der Code in Listing 9.47 stellt keinen Smell in diesem Sinne dar, weil die Fallunterscheidung hier nicht über Objektzustände erfolgt. Deswegen besteht auch nicht die Gefahr, dass sich diese Fallunterscheidung streut. Im Allgemeinen lassen sich spezielle Parser oder endliche Automaten häufig leichter über Fallunterscheidungen umsetzen.

Wie dem auch sei, seien Sie bei Fallunterscheidungen wachsam. Wenn Sie nicht zur Unterscheidung der Zeichen einer Zeichenkette [Anm.: Oder allgemeiner: Zahlen einer Zahlenfolge; z. B. Bytewerten aus einem Bytearray] dienen, liegt häufig ein Code-Smell vor. Das gilt insbesondere dann, wenn man die gleiche Fallunterscheidung an mehreren Stellen im Code wiederfindet.


Rheinwerk Computing - Zum Seitenanfang

9.2.8Javascript ausführenZur nächsten ÜberschriftZur vorigen Überschrift

Es ist außerdem möglich, den Inhalt einer HTML-Seite über Javascript abzufragen und anzupassen, während sie der Webview anzeigt. Dazu dient die Methode stringByEvaluatingJavaScriptFromString:. Sie können diese Methode im Beispielprojekt testen, indem Sie den Button Javascript unten rechts drücken. Geben Sie in dem darauf erscheinenden Dialog einen beliebigen Ausdruck in das Textfeld ein. Wenn Sie danach den Button neben dem Textfeld drücken, wertet der Viewcontroller den Ausdruck aus und gibt das Ergebnis im darunterliegenden Text-View aus (siehe Abbildung 9.13).

Abbildung

Abbildung 9.13 Javascript in der Beispiel-App ausführen

Die Auswertung des Javascript-Ausdrucks erfolgt in der Methode evaluate in der Klasse JavaScriptViewController, die Sie in Listing 9.48 finden.

- (IBAction)evaluate {
NSString *theScript = self.scriptField.text;
NSString *theResult = [self.webView
stringByEvaluatingJavaScriptFromString:theScript];

self.resultView.text = theResult;
}

Listing 9.48 Javascript-Ausdruck im Webview ausführen

Mit Javascript stehen Ihnen viele Möglichkeiten zur Verfügung, die HTML-Seite im Webview zu verändern. Allerdings ist die Übergabe längerer Skripte über die Methode sehr mühselig; es ist viel praktischer, eine Skriptdatei mit Javascript-Funktionen zu erstellen, die sich dann über stringByEvaluatingJavaScriptFromString: aufrufen lassen. Durch die Einbindung von Javascript-Frameworks wie beispielsweise Dojo Toolkit [Anm.: http://dojotoolkit.org] oder jQuery [Anm.: http://jquery.com] können Sie häufig Ihren Entwicklungsaufwand gegenüber einfachem Javascript noch erheblich reduzieren. Sofern Sie den HTML-Quelltext auch erstellt haben, können Sie in der Regel die Javascript-Datei direkt über ein script-Tag einbinden.

Bei fremden HTML-Seiten ist das meistens nicht möglich. Stattdessen können Sie jedoch den Quelltext der Datei über die Methode stringForResource:withExtension: (siehe Listing 9.43) laden und die enthaltenen Anweisungen über stringByEvaluatingJavaScriptFromString: im Webview ausführen. Das Beispielprojekt nutzt dieses Vorgehen, um die Texte aller Links im HTML-Dokument rot zu färben. Dazu verwendet es die Javascript-Bibliothek jQuery, mit der sich solche Aufgabenstellungen besonders einfach erfüllen lassen.

Der Button Highlight Links in der Beispielapplikation löst die Action-Methode highlightLinks aus, die die Javascript-Datei jquery-1.9.1.js aus dem Ressourcenverzeichnis lädt und den Ausdruck $('a').css('color', 'red') ausführt. Damit die Methode jQuery nur einmal lädt, fragt sie vor dem Laden die Existenz der Bibliothek ab, indem sie die Verfügbarkeit der jQuery-Funktion $() überprüft. Das geschieht durch den Javascript-Ausdruck typeof $, der entweder undefined oder function liefert. Die Methode lädt jQuery nur, wenn dieser Ausdruck nicht function liefert.

- (IBAction)highlightLinks {
UIWebView *theView = self.webView;

if(![[theView stringByEvaluatingJavaScriptFromString:
@"typeof $"] isEqualToString:@"function"]) {
NSString *theScript = [self stringForResource:
@"jquery-1.9.1" withExtension:@"js"];

[theView stringByEvaluatingJavaScriptFromString:
theScript];
}
[theView stringByEvaluatingJavaScriptFromString:
@"$('a').css('color', 'red')"];

}

Listing 9.49 jQuery laden und jQuery-Ausdruck ausführen

jQuery

Die jQuery-Funktion $() erlaubt die Suche nach Elementen im HTML-DOM-Baum über CSS-Selektoren. Der (Teil-) Ausdruck $('a') sucht also alle a-Elemente (Links) im Baum und liefert sie als Liste zurück. jQuery stellt eine Reihe von Funktionen bereit, die Sie auf solchen Listen ausführen können. Die im Listing verwendete Funktion css() setzt beispielsweise das Stylesheet-Attribut color auf red.


Rheinwerk Computing - Zum Seitenanfang

9.2.9Ereignisübergabe an die ApplikationZur vorigen Überschrift

Sie können die HTML-Seite in einem Webview auch als eine eigenständige Applikation sehen, der Sie über stringByEvaluatingJavaScriptFromString: aus dem Objective-C-Code Nachrichten senden können. Aber auch die Gegenrichtung – die HTML-Seite sendet Nachrichten an die native Applikation – ist möglich. Natürlich führt eine HTML-Seite in den meisten Fällen keinen kontinuierlich laufenden Code aus. Dennoch gibt es einige Anwendungsfälle, in denen diese Kommunikationsrichtung sinnvoll ist. Dazu gehören beispielsweise Nutzereingaben wie Texteingaben, Klicks, Wischbewegungen oder andere Gesten sowie die Ausführung von Javascript-Code.

Hierfür stellt Apple leider keine direkte Möglichkeit zur Verfügung, so dass Sie sich mit einem Trick behelfen müssen: Dabei rufen Sie aus der HTML-Seite spezielle URLs auf, die Sie über die Methode webView:shouldStartLoadWithRequest:navigationType: des Webview-Delegates herausfiltern und auswerten.

In der Praxis ist es sehr sinnvoll, in diesen URLs ein einheitliches Merkmal zu verwenden, damit die Delegate-Methode sie einfach erkennen kann. Hierfür bieten sich besonders die Bestandteile Schema und Server (siehe Abschnitt 8.4.2, »Komplexe Anfragen«) der URL an. Das Beispielprogramm WebView verwendet hierfür das Schema callback und verwendet den Server zur Angabe der Methode, deren Parameterwert sich im Pfad befindet. Beispielsweise sendet die URL callback://setBarTitle/Dienstag die Nachricht setBarTitle: mit dem Parameterwert @"Dienstag" an den Viewcontroller. In Listing 9.50 finden Sie den Code, der diese URLs auswertet.

- (BOOL)webView:(UIWebView *)inWebView 
shouldStartLoadWithRequest:(NSURLRequest *)inRequest
navigationType:(UIWebViewNavigationType)inType {
NSURL *theURL = inRequest.URL;
NSString *theScheme = theURL.scheme;

if([theScheme isEqualToString:@"callback"]) {
NSString *theMethod =
[theURL.host stringByAppendingString:@":"];
SEL theSelector = NSSelectorFromString(theMethod);
NSString *theParameter =
[theURL.path substringFromIndex:1];

[self performSelector:theSelector
withObject:theParameter];
return NO;
}

else {
return YES;
}
}

Listing 9.50 Methoden über URL-Anfragen aufrufen

Sie können das im Beispielprojekt über den Eintrag Callbacks ausprobieren. Wenn Sie einen Link unterhalb von Titel ändern mit einem Wochentag auswählen, ändern Sie den Titel der Navigationsleiste, der Link Wecker unter Lärm lässt den Alarmton des analogen Weckers erklingen, und über die Links unterhalb von Aktivitätsanzeige können Sie den Netzzugriffsindikator in der Statusleiste an- und ausschalten.

Die HTML-Seite callback.html enthält für diese Links jeweils ein a-Tag mit der entsprechenden URL im href-Attribut, also beispielsweise <a href="callback://setBarTitle/Montag">Montag</a>. Häufig soll der Aufruf jedoch per Javascript erfolgen. Hierfür können Sie die URL-Anfrage starten, indem Sie dem Fenster eine neue URL zuweisen. Eine Javascript-Funktion zum Setzen des Titels finden Sie in Listing 9.51; dort startet die Zuweisung an window.location.href in der Funktion callMethod() die URL-Anfrage.

function callMethod(inName, inParameter) {
var theURL = "callback://" + inName "/" + inParameter;

window.location.href = theURL;
}
function setBarTitle(inTitle) {
callMethod("setBarTitle", inTitle);
}
function playSound(inName) {
callMethod("playSound", inName);
}

Listing 9.51 Start von URL-Anfragen über Javascript

Wenn Sie einen neuen Titel setzen möchten und dabei den Alarmton erklingen lassen möchten, läge dafür folgender Javascript-Code nahe:

function setBarTitleWithSound(inTitle, inSoundName) {
setBarTitle(inTitle);
playSound(inSoundName);
}

Listing 9.52 Titel setzen und Alarmton abspielen

Allerdings spielt die App damit nur den Alarmton ab und ändert nicht den Titel in der Navigationsleiste. Das liegt an den aufeinanderfolgenden Zuweisungen an window.location.href, denn der Webview führt zuerst den laufenden Javascript-Code bis zum Ende aus und lädt dann erst die neue URL. Dadurch kommt nur die zweite URL-Anfrage zum Zug. Die Verwendung von Timern ist eine Möglichkeit, dieses Problem zu umgehen. Eine andere, wesentlich einfachere und elegantere Methode führt die URL-Anfragen über Inlineframes aus. Hierbei erzeugen Sie per Javascript ein neues iframe-Element, setzen die URL als dessen Quelle und fügen es in die Webseite ein.

Inlineframes

Ein Inlineframe (IFrame) ist ein HTML-Element, das den Inhalt einer URL-Anfrage in einem rechteckigen Bereich der aktuellen HTML-Seite enthält. Sie können damit also eine HTML-Seite innerhalb einer anderen anzeigen. Das nutzen beispielsweise Videoplattformen wie YouTube, um die Einbettung ihrer Filme in anderen Webseiten auf einfachem Wege zu ermöglichen.

Sie brauchen dafür nur die Funktion callMethod() aus Listing 9.51 folgendermaßen zu ändern:

function callMethod(inName, inParameter) {
var theURL = "callback://" + inName + "/" + inParameter;
var theFrame = document.createElement("IFRAME");

theFrame.setAttribute("src", theURL);
document.body.appendChild(theFrame);
document.body.removeChild(theFrame);
}

Listing 9.53 URL-Anfragen aus Javascript über einen IFrame starten

Die Funktion aus Listing 9.53 fügt über einen Aufruf von appendChild den IFrame in das body-Element der Seite ein. Das reicht für die Ausführung der URL-Anfrage bereits aus, so dass die Funktion den IFrame in der letzten Anweisung problemlos wieder entfernen kann. Das ist sinnvoll, da andernfalls jede URL-Anfrage zu jeweils einem neuen IFrame in der Webseite führte und dadurch der Speicherbedarf des Webviews anstiege. Mit der neuen Implementierung führt die App nun beide Methodenaufrufe aus.



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