Des questions similaires ont déjà été posées, mais je n’ai jamais trouvé de solution.
Voici ma situation - UIWebView charge une page html distante. Les images utilisées dans les pages Web sont connues au moment de la construction. Pour accélérer le chargement des pages, je souhaite regrouper 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 des fichiers html et image du local - je l'ai déjà fait]
La recommandation la plus proche que j'ai obtenue consistait à utiliser un schéma d'URL personnalisé tel que myapp: //images/img.png dans la page html et dans l'application iOS, intercepter l'URL myapp: // avec la sous-classe NSURLProtocol et remplacer l'image par image. Cela semblait bien en théorie, mais je n’ai pas trouvé d’exemple de code complet démontrant cela.
J'ai des antécédents Java. Je pouvais 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 suffisamment d'expérience avec Objective-C pour le résoudre moi-même dans les délais très brefs que je dispose.
Toute aide serait appréciée.
Ok, voici un exemple de sous-classe NSURLProtocol et de livraison d’une image (image1.png) déjà présente dans le lot. Vous trouverez ci-dessous l'en-tête des sous-classes, l'implémentation, ainsi qu'un exemple d'utilisation de celui-ci dans un viewController (code incomplet) et un fichier html local (pouvant être facilement échangé avec un autre 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 posais cette question moi-même 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 iOS actuelle, veuillez consulter la réponse de sjs. Quand j'ai répondu à la question, cela fonctionnait bien. Il a signalé quelques ajouts utiles et corrigé certains problèmes, alors donnez-lui également des accessoires.
Voici à quoi ça ressemble dans mon simulateur:
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];
NSString *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];
}
file.html
<html>
<body>
<h1>we are loading a custom protocol</h1>
<b>image?</b><br/>
<img src="myapp://image1.png" />
<body>
</html>
Nick Weaver a la bonne idée mais le code dans sa réponse ne fonctionne pas. Il rompt également certaines conventions de dénomination, ne nommez jamais vos propres classes avec le préfixe NS
et suivez la convention consistant à utiliser des acronymes majuscules tels que l'URL dans les noms d'identifiant. Je vais coller avec son nom dans l'intérêt de rendre cela facile à suivre.
Les modifications sont subtiles mais importantes: perdez le request
ivar non attribué et reportez-vous à la demande réelle fournie par NSURLProtocol
et tout fonctionnera bien.
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];
NSString *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 demande. NSURLProtocol
a déjà la demande et vous pouvez y accéder avec la méthode -[NSURLProtocol request]
ou la propriété du même nom. Étant donné que request
ivar dans son code d'origine n'est jamais attribué, il est toujours nil
(et s'il avait été attribué, il aurait dû être publié 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 finalement, -[NSURLProtocol stopLoading]
n'est pas nécessairement une erreur, cela signifie simplement que vous devriez arrêter de travailler sur une réponse, si possible. L'utilisateur peut l'avoir annulé.
J'espère que je comprends votre problème correctement:
1) charger une page Web distante ... et
2) substituer certains fichiers distants par des fichiers dans l'application/build
Droite?
Eh bien, voici ce que je fais (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 (à compiler avec Xcode) avec des balises de style, pour que le contenu in-app/build soit remplacé, défini sur masqué, par exemple:
<div style="display: none;">
<div id="video">
<video width="614" controls webkit-playsinline>
<source src="myvideo.mp4">
</video>
</div>
</div>
• dans le même fichier, fournissez une div de contenu, par exemple.
<div id="content"></div>
• (avec jQuery ici) chargez le contenu actuel du serveur distant et ajoutez votre disque local (ressource importée par Xcode) à votre div cible, par exemple.
<script src="jquery.js"></script>
<script>
$(document).ready(function(){
$("#content").load("http://www.yourserver.com/index-test.html", function(){
$("#video").appendTo($(this).find("#destination"));
});
});
</script>
• déposez les fichiers www (index.html/jquery.js/etc ... utilisez les niveaux de racine pour les tests) dans le projet et connectez-vous à la cible
• le fichier HTML distant (situé ici sur votreserveur.com/index-test.html) ayant un
<base href="http://www.yourserver.com/">
• ainsi qu’un div de destination, par ex.
<div id="destination"></div>
• 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]];
NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[self.myWebView loadHTMLString:content baseURL:baseURL];
Cela me fait plaisir, mieux en conjonction avec https://github.com/rnapier/RNCachingURLProtocol , pour la mise en cache hors connexion. J'espère que cela t'aides. F
L'astuce consiste à fournir l'URL de base explicite à un code HTML existant.
Chargez le code HTML dans une chaîne NSString, utilisez le loadHTMLString: baseURL:
de UIWebView avec l’URL dans votre bundle comme base. Pour charger HTML dans une chaîne, vous pouvez utiliser [NSString stringWithContentsOfURL], mais c'est une méthode synchrone et, sur une connexion lente, le périphérique sera gelé. 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 locale, le GCDWebServer convient à cela:
self.webServer = [[GCDWebServer alloc] init];
[self.webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:
^GCDWebServerResponse *(GCDWebServerRequest *request)
{
NSString *fp = request.URL.path;
if([[NSFileManager defaultManager] fileExistsAtPath:fp]){
NSData *dt = [NSData dataWithContentsOfFile:fp];
NSString *ct = nil;
NSString *ext = request.URL.pathExtension;
BOOL (^IsExtInSide)(NSArray<NSString *> *) = ^(NSArray<NSString *> *pool){
NSUInteger index = [pool indexOfObjectWithOptions:NSEnumerationConcurrent
passingTest:^BOOL(NSString *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:
NSString *fp = [[NSBundle mainBundle] pathForResource:@"picture" ofType:@"jpg" inDirectory:@"www"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d%@", LocalFileServerPort, fp]];
NSString *str = url.absoluteString;
[self.webViewController executeJavascript:[NSString stringWithFormat:@"updateLocalImage('%@')", str]];