47

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

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

[htmlはリモートであることに注意してください。私はいつもローカルからhtmlと画像ファイルの両方をロードするための答えを得ます-私はすでにそれをしました]

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

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

どんな助けでもありがたいです。

4

5 に答える 5

87

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

そして、質問をありがとう!私はかなり長い間これを自分自身に尋ねていました、これを理解するのにかかった時間は毎秒価値がありました。

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

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

ここに画像の説明を入力してください

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>
于 2011-04-06T21:26:31.353 に答える
39

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

変更は微妙ですが重要です。割り当てられていないrequestivarを失い、代わりにによって提供される実際の要求を参照すると、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]彼の元のコードのrequestivarは割り当てられないため、常に割り当てられますnil(割り当てられている場合は、どこかでリリースされているはずです)。そのコードは機能せず、機能しません。

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

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

于 2011-11-27T19:29:08.420 に答える
2

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

1)リモートWebページをロードします...そして

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

右?


さて、私がしていることは次のとおりです(Mobile 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を使用)リモートサーバーから実際のコンテンツをロードし、ローカル(Xcodeでインポートされたアセット)をターゲットdivに追加します。

<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

于 2014-06-05T08:36:24.813 に答える
1

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

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

于 2011-04-06T20:36:06.030 に答える
0

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]];
于 2016-03-22T04:22:11.790 に答える