Im letzten Artikel habe ich ein Beispiel gegeben wie man ganz einfach Inhalte aus dem Web in einem UIWebView anzeigen kann. Der Nachteil hiervon ist -gerade bei größeren Dateien- dass man solange die Seite läd nur eine weiße Seite ohne Inhalt sieht, und irgendwann erscheint dann lötzlich der Inhalt.
Ich habe hier ein Beispiel wie ich es gelöst habe einen Inhalt in einen UIWebView zu laden und für die Dauer des Lade-Vorgangs eine Fortschrittsanzeige anzuzeigen, dies erfordert aber einige Zusatz-Methoden und -Variablen.
So sieht mein ViewController grob im Interface-Builder aus, ich habe ganz einfach einen UIWebView reingezogen, darauf habe ich ein UIProgressView platziert, darunter jeweils links und rechts ein Label:

In meiner .h Datei habe ich einige Instanz-Variablen und Outlets definiert:
@interface ProgressViewController : UIViewController {
UIWebView *webView;
UIProgressView *progressBar;
UILabel *prozentLabel;
UILabel *groesseLabel;
NSMutableData *responseData;
NSString *mimeType, *textEncoding;
NSInteger dateiGroesse;
NSURLConnection *urlConnection;
}
@property (nonatomic, retain) IBOutlet UIWebView *webView;
@property (nonatomic, retain) IBOutlet UIProgressView *progressBar;
@property (nonatomic, retain) IBOutlet UILabel *prozentLabel;
@property (nonatomic, retain) IBOutlet UILabel *groesseLabel;
@property (nonatomic, retain) NSURLConnection *urlConnection;
- (NSString *)groessenFormatierung:(NSInteger)size;
Die Labels, den ProgressView und den WebView müsst Ihr natürlich mit den entsprechenden Outlets verbinden.
In der .m-Datei geht es nun etwas kompexer zu, allerdings auch nicht so tragisch wenn man es genauer anschaut
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.scalesPageToFit = YES;
// Progress etc. erst einmal verstecken
self.progressBar.hidden = YES;
self.prozentLabel.hidden = YES;
self.groesseLabel.hidden = YES;
// NSURLConnection erstellen
NSURL *url = [NSURL URLWithString:@"http://www.domain.de/testDatei.pdf"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
In der viewDidLoad-Methode habe ich die 2 Labels und den ProgressView versteckt. Anschliessend wird erst eine NSURL, dann ein NSMutableURLRequest und schließlich eine NSURLConnection erstellt und der Delegate auf sich selbst gesetzt. Das Programm fängt gleich an Daten zu laden und feuert dann folgende Delegate-Methode ab wenn eine Response vom Server empfangen wird:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
responseData = [[NSMutableData alloc] init];
mimeType = [[response MIMEType] retain];
textEncoding = [[response textEncodingName] retain];
dateiGroesse = [response expectedContentLength];
self.groesseLabel.hidden = NO;
if (dateiGroesse > 0) {
self.progressBar.hidden = NO;
self.prozentLabel.hidden = NO;
}
}
In dieser Methode connection:didReceiveResponse: erstelle ich zunächst ein NSMutableData-Objekt und weise es der Instanz-Variablen responseData zu (hier werden nach und nach die Daten empfangen). Da uns die Response auch noch nützliche Infos wie den MIME-Typ und die Text-Kodierung zurückgeben (und diese später beim Befüllen des WebViews benötigt werden) speichere ich diese auch in den Variablen mimeType und textEncoding.
Nun kommen wir zu dem Teil der nur ab und an klappt: mittels [response expectedContentLength] versuche ich die geschätze Größe des Downloads / der Daten zu bekommen. Bei meinen Versuchen hat das nicht geklappt, Ihr müsst testen ob das bei Euch hinhaut. Ich habe mir vorher mit einer anderen Methode die Gesamtgröße der Datei geholt und in der Variablen “dateiGroesse” gespeichert. Es ist natürlich wichtig zu wissen wie groß der Download insgesamt ist, daraus berechnen wir ja dann den aktuellen Fortschritt.
Abschliessend zeige ich die 2 Labels und den ProgressView an, denn in den nächsten Delegate-Methoden geht es um das Empfangen von Daten:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
// Wenn Größe vorhanden ist dann Fortschritt aktualisieren
if (dateiGroesse > 0) {
NSInteger aktuelleBytes = [responseData length];
float prozent = 100 * aktuelleBytes / dateiGroesse;
if (prozent > 100) {
prozent = 100.0;
}
self.prozentLabel.text = [NSString stringWithFormat:@"%.1f %@", prozent, @"%"];
self.groesseLabel.text = [NSString stringWithFormat:@"%@ / %@", [self groessenFormatierung:aktuelleBytes], [self groessenFormatierung:dateiGroesse]];
self.progressBar.progress = prozent / 100;
}
}
Diese Methode connection:didReceiveData: wird jedesmal aufgerufen wenn ein Teil der Daten empfangen wird. Genau dann soll auch der ProgressView aktualisiert werden. Der wichtigste Teil aber ist erst einmal die empfangenen Daten (data) den insgesamt empfangenen Daten hinzuzufügen (responseData).
Danach kommen einfache mathematische Berechnungen über den aktuellen Fortschritt. In den Varablen aktuelleBytes und dateiGroesse sind jeweils die bereits empfangenen Bytes bzw. die Gesamtgroesse des Downloads hinterlegt – anhand diesen kann man den Fortschritt (in Prozent) ermitteln.
Ich bediene mich noch einer kleinen Hilfsmethode groessenFormatierung: welche ich ganz unten abdrucke. Diese stellt einfach nur eine Anzahl Bytes in einer Formatierung xx kB oder xx MB dar.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self _connectionFinished];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Netzwerk-Fehler"
message:@"Probleme beim Abrufen der Datei"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
In der Methode connection:didFailWithError: wird ein Fehler ausgegeben falls z.B. die Verbindung abbricht. Hierzu gibt es nicht viel zu sagen
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self _connectionFinished];
if (dateiGroesse > 0) {
self.progressBar.hidden = YES;
self.prozentLabel.hidden = YES;
self.groesseLabel.hidden = YES;
}
[self.webView loadData:responseData
MIMEType:mimeType
textEncodingName:textEncoding
baseURL:[NSURL URLWithString:@"http://www.domain.de/testDatei.pdf"]];
}
Last but not least wird die Methode connectionDidFinishLoading: aufgerufen wenn alle Daten empfangen wurden. Hier werden die beiden Labels und der ProgressView versteckt so dass der UIWebView angezeigt werden kann. In dem letzten Methoden-Aufruf wird der WebView gefüllt mit den empfangenen Daten. Es wird der MIME-Typ und die Text-Kodierung benötigt, diese haben wir ja aber weiter oben in 2 Variablen gespeichert. Etwas unschön an meinem Beispiel ist dass ich die URL nicht in einer weiteren Instanz-Variable gespeichert habe, das könnt Ihr ja bei Euch noch ändern.
Update:
Die Methoden welche die Verbindung abbrechen rufen diese Hilfsfunktion auf welche die Connection abbricht und released. So werden Memory Leaks vermieden:
- (void)_connectionFinished {
if (self.urlConnection != nil) {
[self.urlConnection cancel];
self.urlConnection = nil;
}
}
Hier noch die Hilfsfunktion um die Anzahl der Bytes etwas schöner dazustellen:
- (NSString *)groessenFormatierung:(NSInteger)size {
NSString *ret = @"";
if (size > 0) {
// Größer als 1024? -> dann in kB anzeigen
if (size > 1024) {
// Größer als 1048576? -> dann in MB anzeigen
if (size > 1048576) {
// in MB anzeigen
float inMB = size / 1048576;
ret = [NSString stringWithFormat:@"%.1f MB", inMB];
} else {
// in kB anzeigen
float inKB = size / 1024;
ret = [NSString stringWithFormat:@"%.1f kB", inKB];
}
} else {
// In Bytes anzeigen
ret = [NSString stringWithFormat:@"%i B", size];
}
} else {
ret = [NSString stringWithFormat:@"0 MB"];
}
return ret;
}
Ich hoffe das war einigermaßen verständlich. Falls Ihr Probleme bei dem einabauen in Eure Projekte habt könnt Ihr Euch gerne mit mir in Verbindung setzen bzw. diesen Beitrag kommentieren.
Ein sehr gutes Tutorial – vielen Dank hierfür.
Kleiner Schreibfehler in der “(void)connectionDidFinishLoading”, Zeile #3:
“if (dateiGroesse) > 0) {”
Viele Grüße
ezod