46 votes

iOS WebView html à distance avec des fichiers image locaux

Des questions similaires ont été posées, mais je n'ai jamais pu trouver une solution.

Voici ma situation - mon UIWebView charge une page html distante. Les images utilisées dans les pages web sont connus au moment de la construction. Afin de rendre le chargement de la page plus rapidement, je veux package des fichiers d'image dans l'application iOS et de substitution lors de l'exécution.

[Veuillez noter que le code html est à distance. Je reçois toujours des réponses pour le chargement html et les fichiers d'image de locaux - j'ai déjà fait]

Le plus proche de la recommandation I a été d'utiliser une url personnalisée régime tel que myapp://images/img.png dans le code html de la page et dans l'application iOS, intercepter le myapp:// URL avec NSURLProtocol sous-classe et de remplacer l'image par une image locale. Sonne bien en théorie, mais je n'ai pas rencontré un code complet de l'exemple de le démontrer.

J'ai Java arrière-plan. Je pourrais le faire facilement pour Android à l'aide d'un Fournisseur de Contenu Personnalisé. Je suis sûr d'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 le peu de temps que j'ai.

Toute aide sera appréciée.

85voto

Nick Weaver Points 30418

Ok, voici un exemple comment la sous-classe NSURLProtocol et de fournir une image (image1.png), qui est déjà dans le bundle. Ci-dessous le sous-classes " en-tête, la mise en œuvre, ainsi qu'un exemple de comment l'utiliser dans un viewController(code incomplète) et un fichier html local(qui peut être facilement échangés avec une seule télécommande). J'ai appelé le protocole personnalisé: myapp:// comme vous pouvez le voir dans le fichier html dans le fond.

Et merci pour la question! J'ai demandé moi-même pour un temps assez long, le temps qu'il a fallu pour comprendre cela a été vaut chaque seconde.

EDIT: Si quelqu'un a des difficultés à faire mon code à exécuter dans le cadre actuel de la version d'iOS, veuillez jeter un oeil à la réponse de see. Lorsque j'ai répondu à la question, il travaillait bien. Il est à souligner certains des ajouts utiles et corrigé certaines questions, afin de lui donner des accessoires pour lui.

C'est à quoi il ressemble dans mon simulateur:

enter image description here

NSURLProtocolCustom.h

@interface NSURLProtocolCustom : NSURLProtocol
{
    NSURLRequest *request;
}

@property (nonatomic, retain) NSURLRequest *request;

@end

NSURLProtocolCustom.m

#import "NSURLProtocolCustom.h"

@implementation NSURLProtocolCustom

@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

SomeViewController.h

@interface NSUrlProtocolSubclassViewController : UIViewController {
    UIWebView *webView;
}

@property (nonatomic, retain) UIWebView *webView;

@end

SomeViewController.m

...

@implementation NSUrlRequestSubclassViewController

@synthesize webView;

- (void)awakeFromNib
{
    self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(20, 20, 280, 420)] autorelease];
    [self.view addSubview:webView];
}

- (void)viewDidLoad
{   
    // ----> IMPORTANT!!! :) <----
    [NSURLProtocol registerClass:[NSURLProtocolCustom 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 protocl</h1>
    <b>image?</b><br/>
    <img src="myapp://image1.png" />
<body>
</html>

39voto

Sami Samhuri Points 617

Nick Weaver a la bonne idée, mais le code dans sa réponse ne fonctionne pas. Il se casse, certaines conventions de nommage ainsi, jamais le nom de vos propres classes avec l' NS préfixe, et de suivre la convention de capitaliser les acronymes tels que les URL en identifiant les noms. Je vais m'en tenir w/ son nom dans le but de faire ce facile à suivre.

Les modifications sont subtiles, mais importantes: perdre la non affecté request ivar et, au lieu de vous référer à la demande réelle fournie par NSURLProtocol et il fonctionne très 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 du Pseudo-code est que les sous-classes de NSURLProtocol n'avez pas besoin de stocker la demande. NSURLProtocol déjà a la demande et vous pouvez y accéder avec la méthode -[NSURLProtocol request] ou la propriété du même nom. Depuis l' request ivar dans son code d'origine n'est jamais affecté, il est toujours nil (et si c'était assigné, il doit avoir été publié quelque part). Ce code ne peut pas et ne fonctionne pas.

Deuxièmement, je vous recommande la lecture du fichier de données avant la création de la réponse et en passant [data length] que la longueur du contenu au lieu de -1.

Et enfin, -[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 avoir été supprimée.

2voto

GermanGangsta Points 1

J'espère que je suis la compréhension de votre problème correctement:

1) charger une page web à distance et ...

2) le remplacement de certains actifs à distance avec des fichiers au sein de l'application/build

Droit?


Eh bien, ce que je fais est la suivante (je l'utilise pour les vidéos à cause de la mise en cache de la limite de 5 MO sur le navigateur Safari Mobile, mais je pense que toute les autres DOM contenu devrait fonctionner de la même manière):


• création d'un local (pour être compilé avec Xcode) de la page HTML avec les balises de style, pour le in-app/créer du contenu pour être remplacé, la valeur hidden, 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 de fourniture d'un contenu de la div, par exemple

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


• (à l'aide de jQuery ici) charger le contenu à partir du serveur distant et l'ajout de votre région (Xcode importés de l'actif) à votre cible div, 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époser le www fichiers (index.html / jquery.js / etc ... utilisation de la racine de niveaux pour les tests) dans le projet et se connecter à la cible


• la télécommande fichier HTML (ici situé à yourserver.com/index-test.html) ayant une

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


• ainsi qu'une destination div, par exemple

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


• et enfin dans votre projet Xcode, charge le HTML en 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];

Fonctionne un régal pour moi, le meilleur en conjonction avec https://github.com/rnapier/RNCachingURLProtocol, pour la mise en cache hors connexion. Espérons que cette aide. F

1voto

Seva Alekseyev Points 31812

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

Chargez le code HTML dans une chaîne NSS, utilisez le loadHTMLString: baseURL: 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 .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X