iOS WebView html distant avec fichiers image locaux

Des questions similaires ont déjà été posées, mais je n’ai jamais pu trouver de solution.

Voici ma situation – mon UIWebView charge une page HTML distante. Les images utilisées dans les pages Web sont connues au moment de la construction. Afin d’accélérer le chargement de la page, je souhaite empaqueter les fichiers image dans l’application iOS et les remplacer au moment de l’exécution.

[Veuillez noter que le code HTML est distant. Je reçois toujours des réponses pour le chargement de fichiers HTML et d’images à partir de fichiers locaux – je l’ai déjà fait]

La recommandation la plus proche a été d’utiliser un schéma d’URL personnalisé tel que myapp: //images/img.png dans la page html et dans l’application iOS, d’intercepter l’URL myapp: // avec la sous-classe NSURLProtocol et de remplacer l’image par un local image. Cela semblait bien en théorie, mais je ne suis pas tombé sur un exemple de code complet démontrant cela.

J’ai un fond Java Je pourrais le faire facilement pour Android en utilisant un fournisseur de contenu personnalisé. Je suis sûr qu’une solution similaire doit exister pour iOS / Objective-C. Je n’ai pas assez d’expérience en Objective-C pour le résoudre moi-même dans un court laps de temps.

Toute aide serait appréciée.

Ok voici un exemple de la sous-classe NSURLProtocol et de fournir une image ( image1.png ) qui est déjà dans le bundle. Vous trouverez ci-dessous l’en-tête des sous-classes, l’implémentation ainsi qu’un exemple d’utilisation dans un viewController (code incomplet) et un fichier html local (qui peut être facilement échangé avec un fichier distant). J’ai appelé le protocole personnalisé: myapp:// comme vous pouvez le voir dans le fichier HTML en bas.

Et merci pour la question! Je me demandais cela depuis assez longtemps, le temps qu’il fallait pour comprendre cela valait chaque seconde.

EDIT: Si quelqu’un a des difficultés à exécuter mon code sous la version actuelle d’iOS, jetez un oeil à la réponse de sjs. Quand j’ai répondu à la question, ça marchait bien. Il montre quelques ajouts utiles et corrige certains problèmes, alors donnez-lui aussi des accessoires.

Voici comment ça se passe dans mon simulateur:

entrer la description de l'image ici

MyCustomURLProtocol.h

 @interface MyCustomURLProtocol : NSURLProtocol { NSURLRequest *request; } @property (nonatomic, retain) NSURLRequest *request; @end 

MyCustomURLProtocol.m

 #import "MyCustomURLProtocol.h" @implementation MyCustomURLProtocol @synthesize request; + (BOOL)canInitWithRequest:(NSURLRequest*)theRequest { if ([theRequest.URL.scheme caseInsensitiveCompare:@"myapp"] == NSOrderedSame) { return YES; } return NO; } + (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest { return theRequest; } - (void)startLoading { NSLog(@"%@", request.URL); NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil]; NSSsortingng *imagePath = [[NSBundle mainBundle] pathForResource:@"image1" ofType:@"png"]; NSData *data = [NSData dataWithContentsOfFile:imagePath]; [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; [response release]; } - (void)stopLoading { NSLog(@"something went wrong!"); } @end 

MyCustomProtocolViewController.h

 @interface MyCustomProtocolViewController : UIViewController { UIWebView *webView; } @property (nonatomic, retain) UIWebView *webView; @end 

MyCustomProtocolViewController.m

 ... @implementation MyCustomProtocolViewController @synthesize webView; - (void)awakeFromNib { self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(20, 20, 280, 420)] autorelease]; [self.view addSubview:webView]; } - (void)viewDidLoad { // ----> IMPORTANT!!! :) <---- [NSURLProtocol registerClass:[MyCustomURLProtocol class]]; NSString * localHtmlFilePath = [[NSBundle mainBundle] pathForResource:@"file" ofType:@"html"]; NSString * localHtmlFileURL = [NSString stringWithFormat:@"file://%@", localHtmlFilePath]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:localHtmlFileURL]]]; NSString *html = [NSString stringWithContentsOfFile:localHtmlFilePath encoding:NSUTF8StringEncoding error:nil]; [webView loadHTMLString:html baseURL:nil]; } 

fichier.html

   

we are loading a custom protocol

image?

Nick Weaver a la bonne idée mais le code dans sa réponse ne fonctionne pas. Il enfreint également certaines conventions de dénomination, ne nommez jamais vos propres classes avec le préfixe NS et respectez la convention consistant à capitaliser les acronymes tels que les URL dans les noms d’identificateurs. Je vais m’en tenir à son nom dans l’intérêt de le rendre facile à suivre.

Les modifications sont subtiles mais importantes: perdez la request non assignée ivar et faites plutôt référence à la requête réelle fournie par NSURLProtocol et cela fonctionne NSURLProtocol .

NSURLProtocolCustom.h

 @interface NSURLProtocolCustom : NSURLProtocol @end 

NSURLProtocolCustom.m

 #import "NSURLProtocolCustom.h" @implementation NSURLProtocolCustom + (BOOL)canInitWithRequest:(NSURLRequest*)theRequest { if ([theRequest.URL.scheme caseInsensitiveCompare:@"myapp"] == NSOrderedSame) { return YES; } return NO; } + (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest { return theRequest; } - (void)startLoading { NSLog(@"%@", self.request.URL); NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil]; NSSsortingng *imagePath = [[NSBundle mainBundle] pathForResource:@"image1" ofType:@"png"]; NSData *data = [NSData dataWithContentsOfFile:imagePath]; [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; [response release]; } - (void)stopLoading { NSLog(@"request cancelled. stop loading the response, if possible"); } @end 

Le problème avec le code de Nick est que les sous-classes de NSURLProtocol n’ont pas besoin de stocker la requête. NSURLProtocol déjà la requête et vous pouvez y accéder avec la méthode -[NSURLProtocol request] ou la propriété du même nom. Comme la request ivar dans son code d’origine n’est jamais affectée, elle est toujours nil (et si elle était assignée, elle devrait avoir été publiée quelque part). Ce code ne peut pas et ne fonctionne pas.

Deuxièmement, je recommande de lire les données du fichier avant de créer la réponse et de transmettre [data length] comme longueur de contenu attendue au lieu de -1.

Et enfin, -[NSURLProtocol stopLoading] n’est pas nécessairement une erreur, cela signifie simplement que vous devez arrêter de travailler sur une réponse, si possible. L’utilisateur peut l’avoir annulé.

J’espère que je comprends bien votre problème:

1) charger une page Web distante … et

2) remplacer certains actifs distants par des fichiers dans l’application / la version

Droite?


Eh bien, ce que je fais est comme suit (je l’utilise pour les vidéos en raison de la limite de mise en cache de 5 Mo sur Mobile Safari, mais je pense que tout autre contenu DOM devrait fonctionner de la même manière):

• créer une page HTML locale (à comstackr avec Xcode) avec des balises de style, pour que le contenu in-app / build soit substitué, défini sur hidden, par exemple:

 

• dans le même fichier, fournir un contenu div, par exemple

 

• (en utilisant jQuery ici), chargez le contenu réel du serveur distant et ajoutez votre fichier local (ressource imscope Xcode) à votre div cible, par exemple

   

• déposez les fichiers www (index.html / jquery.js / etc … utilisez les niveaux racine pour les tests) dans le projet et connectez-vous à la cible

• le fichier HTML distant (ici situé sur yourserver.com/index-test.html) ayant un

  

• ainsi qu’une destination div, par exemple

 

• et enfin dans votre projet Xcode, chargez le code HTML local dans la vue Web

 self.myWebView = [[UIWebView alloc]init]; NSURL *baseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; NSSsortingng *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"]; NSSsortingng *content = [NSSsortingng ssortingngWithContentsOfFile:path encoding:NSUTF8SsortingngEncoding error:nil]; [self.myWebView loadHTMLSsortingng:content baseURL:baseURL]; 

Cela me convient parfaitement , en liaison avec https://github.com/rnapier/RNCachingURLProtocol , pour la mise en cache hors ligne. J’espère que cela t’aides. F

L’astuce consiste à fournir l’URL de base explicite à un code HTML existant.

Chargez le HTML dans un NSSsortingng, utilisez loadHTMLSsortingng: baseURL: avec l’URL dans votre bundle comme base. Pour charger du HTML dans une chaîne, vous pouvez utiliser [NSSsortingng ssortingngWithContentsOfURL], mais c’est une méthode synchrone, et sur une connexion lente, il gèlera le périphérique. L’utilisation d’une requête asynchrone pour charger le code HTML est également possible, mais plus complexe. Lisez sur NSURLConnection .

NSURLProtocol est un bon choix pour UIWebView , mais jusqu’à présent, WKWebView ne le supportait toujours pas. Pour WKWebView, nous pouvons créer un serveur HTTP local pour gérer la demande de fichier local, GCDWebServer convient bien:

 self.webServer = [[GCDWebServer alloc] init]; [self.webServer addDefaultHandlerForMethod:@"GET" requestClass:[GCDWebServerRequest class] processBlock: ^GCDWebServerResponse *(GCDWebServerRequest *request) { NSSsortingng *fp = request.URL.path; if([[NSFileManager defaultManager] fileExistsAtPath:fp]){ NSData *dt = [NSData dataWithContentsOfFile:fp]; NSSsortingng *ct = nil; NSSsortingng *ext = request.URL.pathExtension; BOOL (^IsExtInSide)(NSArray *) = ^(NSArray *pool){ NSUInteger index = [pool indexOfObjectWithOptions:NSEnumerationConcurrent passingTest:^BOOL(NSSsortingng *obj, NSUInteger idx, BOOL *stop) { return [ext caseInsensitiveCompare:obj] == NSOrderedSame; }]; BOOL b = (index != NSNotFound); return b; }; if(IsExtInSide(@[@"jpg", @"jpeg"])){ ct = @"image/jpeg"; }else if(IsExtInSide(@[@"png"])){ ct = @"image/png"; } //else if(...) // other exts return [GCDWebServerDataResponse responseWithData:dt contentType:ct]; }else{ return [GCDWebServerResponse responseWithStatusCode:404]; } }]; [self.webServer startWithPort:LocalFileServerPort bonjourName:nil]; 

Lorsque vous spécifiez le chemin du fichier local, ajoutez le préfixe du serveur local:

 NSSsortingng *fp = [[NSBundle mainBundle] pathForResource:@"picture" ofType:@"jpg" inDirectory:@"www"]; NSURL *url = [NSURL URLWithSsortingng:[NSSsortingng ssortingngWithFormat:@"http://127.0.0.1:%d%@", LocalFileServerPort, fp]]; NSSsortingng *str = url.absoluteSsortingng; [self.webViewController executeJavascript:[NSSsortingng ssortingngWithFormat:@"updateLocalImage('%@')", str]];