web-dev-qa-db-ja.com

iOS WebViewリモートHTMLとローカル画像ファイル

同様の質問が以前に聞かれましたが、解決策を見つけることができませんでした。

これが私の状況です-私のUIWebViewはリモートhtmlページをロードします。 Webページで使用される画像は、ビルド時に知られています。ページの読み込みを高速化するために、iOSアプリケーションに画像ファイルをパッケージ化し、実行時にそれらを置き換えたいと思います。

[htmlはリモートであることに注意してください。ローカルからhtmlファイルと画像ファイルの両方を読み込むと、常に答えが返ってきます。

私が得た最も近い推奨事項は、htmlページおよびiOSアプリケーションでmyapp://images/img.pngなどのカスタムURLスキームを使用し、NSURLProtocolサブクラスでmyapp:// URLをインターセプトし、画像をローカルに置き換えることでした。画像。理論的には良さそうに見えますが、これを示す完全なコード例はありません。

Javaバックグラウンドがあります。カスタムコンテンツプロバイダーを使用してAndroidに対してこれを簡単に行うことができます。 iOS/Objective-Cにも同様のソリューションが存在するはずです。 Objective-Cの経験が足りないので、短期間で自分で解決できません。

任意の助けをいただければ幸いです。

45
CM Subram

OKは、 NSURLProtocol をサブクラス化して、既にバンドルにあるイメージ(image1.png)を配信する方法の例です。以下は、サブクラスのヘッダー、実装、およびviewController(不完全なコード)およびローカルhtmlファイル(リモートファイルと簡単に交換可能)での使用方法の例です。カスタムプロトコルを呼び出しました:myapp://下部のhtmlファイルで確認できます。

そして質問をありがとう!私はこれを非常に長い間求めていましたが、これを理解するのにかかった時間は毎秒の価値がありました。

EDIT:誰かが私のコードを現在のiOSバージョンで実行できない場合は、sjsからの回答をご覧ください。質問に答えたとき、それは機能していました。彼はいくつかの有用な追加を指摘し、いくつかの問題を修正しているので、彼にも小道具を渡します。

これは私のシミュレータでどのように見えるかです:

enter image description here

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>
84
Nick Weaver

ニック・ウィーバーは正しい考えを持っていますが、彼の答えのコードは機能しません。また、いくつかの命名規則に違反し、独自のクラスにNSプレフィックスを付けないようにし、識別子名にURLなどの頭字語を大文字にするという規則に従います。これをわかりやすくするために、彼の命名にこだわるつもりです。

変更は微妙ですが重要です。未割り当てのrequest ivarを失い、代わりに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

Nickのコードの問題は、NSURLProtocolのサブクラスがリクエストを保存する必要がないことです。 NSURLProtocolにはすでにリクエストがあり、メソッド-[NSURLProtocol request]または同じ名前のプロパティを使用してアクセスできます。元のコードのrequest ivarは決して割り当てられないため、常にnilになります(割り当てられた場合はどこかにリリースされているはずです)。そのコードは機能せず、機能しません。

第二に、応答を作成する前にファイルデータを読み取り、-1ではなく[data length]を予想コンテンツ長として渡すことをお勧めします。

最後に、-[NSURLProtocol stopLoading]は必ずしもエラーではなく、可能であれば応答の作業を停止することを意味します。ユーザーがキャンセルした可能性があります。

39
Sami Samhuri

私はあなたの問題を正しく理解していることを望みます:

1)リモートWebページをロード...および

2)特定のリモートアセットをapp/build内のファイルに置き換えます

正しい?


さて、私がやっていることは次のとおりです(モバイルSafariでは5MBのキャッシュ制限のためにビデオに使用していますが、他のDOMコンテンツも同様に機能するはずです):


•スタイルタグを使用してローカル(Xcodeでコンパイルされる)HTMLページを作成し、アプリ内/ビルドコンテンツを置換します。非表示に設定、例:

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


•同じファイルでコンテンツdivを指定します。例:

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


•(ここでjQueryを使用)リモートサーバーから実際のコンテンツをロードし、ターゲットdivにローカル(Xcodeインポートされたアセット)を追加します。

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


•プロジェクトにwwwファイル(index.html/jquery.js /など...テストにルートレベルを使用)をドロップして接続しますターゲットへ


•リモートHTMLファイル(ここではyourserver.com/index-test.htmlにあります)には、

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


•および宛先div、たとえば

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


•最後にXcodeプロジェクトで、ローカルHTMLを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];

https://github.com/rnapier/RNCachingURLProtocol と組み合わせて使用​​すると、オフラインキャッシュに最適です。お役に立てれば。 F

2
GermanGangsta

トリックは、明示的なベースURLを既存のHTMLに提供することです。

NSStringにHTMLをロードし、UIWebViewのloadHTMLString: baseURL:バンドルへのURLをベースとして。 HTMLを文字列にロードするには、[NSString stringWithContentsOfURL]を使用できますが、これは同期メソッドであり、接続が遅いとデバイスがフリーズします。非同期リクエストを使用してHTMLをロードすることもできますが、より複雑です。 NSURLConnectionを読んでください。

1
Seva Alekseyev

NSURLProtocolUIWebViewに適していますが、これまではWKWebViewはまだサポートしていません。 WKWebViewの場合、ローカルファイルリクエストを処理するローカルHTTPサーバーを構築できます。 GCDWebServer はこれに適しています:

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

ローカルファイルのファイルパスを指定する場合、ローカルサーバープレフィックスを追加します。

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]];
0
Albert Zhang