iphone html image uiwebview nsurlprotocol

iphone - iOS WebView html remoto con archivos de imagen locales



uiwebview nsurlprotocol (5)

El truco es proporcionar la URL base explícita a un HTML existente.

Cargue el HTML en un NSString, use loadHTMLString de loadHTMLString: baseURL: con la URL en su paquete como base. Para cargar HTML en una cadena, puede usar [NSString stringWithContentsOfURL], pero ese es un método sincrónico, y en conexión lenta congelará el dispositivo. Usar una solicitud asíncrona para cargar el HTML también es posible, pero más complicado. Lea sobre NSURLConnection .

Se han hecho preguntas similares antes, pero nunca pude encontrar una solución.

Aquí está mi situación: mi UIWebView carga una página html remota. Las imágenes utilizadas en las páginas web son conocidas durante el tiempo de compilación. Para que la página se cargue más rápido, quiero empaquetar los archivos de imagen en la aplicación de iOS y sustituirlos en tiempo de ejecución.

[Tenga en cuenta que el html es remoto. Siempre recibo respuestas para cargar archivos html y de imágenes locales, ya lo he hecho]

La recomendación más cercana que recibí fue utilizar un esquema de URL personalizado como myapp: //images/img.png en la página html y en la aplicación de iOS, interceptar la URL myapp: // con la subclase NSurLProtocol y reemplazar la imagen con un local imagen. En teoría, sonaba bien, pero no he encontrado un ejemplo de código completo que demuestre esto.

Tengo antecedentes Java. Podría hacer esto fácilmente para Android usando un proveedor de contenido personalizado. Estoy seguro de que debe existir una solución similar para iOS / Objective-C. No tengo suficiente experiencia en Objective-C para resolverlo yo mismo en el corto espacio de tiempo que tengo.

Cualquier ayuda será apreciada.


Espero comprender su problema correctamente:

1) cargar una página web remota ... y

2) sustituir ciertos activos remotos con archivos dentro de la aplicación / construcción

¿Derecha?

Bueno, lo que estoy haciendo es lo siguiente (lo uso para videos debido al límite de almacenamiento en caché de 5MB en Mobile Safari, pero creo que cualquier otro contenido DOM debería funcionar por igual):


• crear una página HTML local (que se compilará con Xcode) con etiquetas de estilo, para que el contenido integrado en la aplicación / compilación sea sustituido y configurado como oculto, por ejemplo:

<div style="display: none;"> <div id="video"> <video width="614" controls webkit-playsinline> <source src="myvideo.mp4"> </video> </div> </div>


• en el mismo archivo, suministre un div de contenido, por ej.

<div id="content"></div>


• (usando jQuery aquí) cargue el contenido real del servidor remoto y agregue su local (activo importado de Xcode) a su div de destino, por ej.

<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>


• soltar los archivos www (index.html / jquery.js / etc ... usar niveles raíz para probar) en el proyecto y conectarse al objetivo


• el archivo HTML remoto (aquí ubicado en yourserver.com/index-test.html) con una

<base href="http://www.yourserver.com/">


• así como un div de destino, por ejemplo

<div id="destination"></div>


• y finalmente en tu proyecto Xcode, carga el HTML local en la vista 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];

Funciona bien para mí, lo mejor en conjunción con https://github.com/rnapier/RNCachingURLProtocol , para el almacenamiento en caché sin conexión. Espero que esto ayude. F


Nick Weaver tiene la idea correcta, pero el código en su respuesta no funciona. También rompe algunas convenciones de nomenclatura, nunca nombre sus propias clases con el prefijo NS , y siga la convención de poner en mayúscula las siglas como URL en los nombres de los identificadores. Me quedaré con su nombre con el interés de hacer que esto sea fácil de seguir.

Los cambios son sutiles pero importantes: pierda la request no asignada ivar y, en su lugar, consulte la solicitud real proporcionada por NSURLProtocol y funciona 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]; 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

El problema con el código de Nick es que las subclases de NSURLProtocol no necesitan almacenar la solicitud. NSURLProtocol ya tiene la solicitud y puede acceder con el método -[NSURLProtocol request] o la propiedad del mismo nombre. Dado que nunca se asigna la request ivar en su código original, siempre es nil (y si se asignó, debería haber sido publicada en alguna parte). Ese código no puede y no funciona.

En segundo lugar, recomiendo leer los datos del archivo antes de crear la respuesta y pasar [data length] como la longitud del contenido esperado en lugar de -1.

Y, por último, -[NSURLProtocol stopLoading] no es necesariamente un error, solo significa que debe dejar de trabajar en una respuesta, si es posible. El usuario puede haberlo cancelado.


NSURLProtocol es una buena opción para UIWebView , pero hasta ahora el WKWebView aún no lo admite. Para WKWebView podemos construir un servidor HTTP local para manejar la solicitud de archivo local, el GCDWebServer es bueno para esto:

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];

Cuando especifique la ruta del archivo local, agregue el prefijo del servidor 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]];


Ok aquí hay un ejemplo de cómo subclasificar NSURLProtocol y entregar una imagen ( image1.png ) que ya está en el paquete. A continuación se muestra el encabezado de las subclases, la implementación y un ejemplo de cómo usarlo en viewController (código incompleto) y en un archivo html local (que se puede intercambiar fácilmente con uno remoto). He llamado al protocolo personalizado: myapp:// como puede ver en el archivo html en la parte inferior.

Y gracias por la pregunta! Estuve preguntándome esto por un tiempo bastante largo, el tiempo que tomó resolver esto valió la pena cada segundo.

EDITAR: Si alguien tiene dificultades para ejecutar mi código con la versión actual de iOS, eche un vistazo a la respuesta de sjs. Cuando respondí la pregunta, estaba funcionando. Está señalando algunas adiciones útiles y corrigió algunos problemas, así que también le ofrezca accesorios.

Así es como se ve en mi simulador:

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>