带有本地图像文件的iOS WebView远程html

时间:2011-04-06 19:59:47

标签: iphone html image uiwebview nsurlprotocol

以前曾提出过类似的问题,但我找不到解决办法。

这是我的情况 - 我的UIWebView加载了一个远程html页面。网页中使用的图像在构建时是已知的。为了加快页面加载速度,我想将图像文件打包到iOS应用程序中并在运行时替换它们。

[请注意,html是远程的。我总是得到从本地加载html和图像文件的答案 - 我已经这样做了]

我得到的最接近的建议是在html页面和iOS应用程序中使用自定义URL方案,例如myapp://images/img.png,使用NSURLProtocol子类截取myapp:// URL并替换图像与当地的形象。在理论上听起来不错,但我没有遇到一个完整的代码示例来证明这一点。

我有Java背景。我可以使用自定义内容提供商轻松地为Android做到这一点。我相信iOS / Objective-C必须存在类似的解决方案。我没有足够的Objective-C经验可以在很短的时间内自己解决它。

任何帮助将不胜感激。

5 个答案:

答案 0 :(得分:84)

好的这里是一个如何子类化NSURLProtocol并传递已经在捆绑中的图像( image1.png )的示例。下面是子类的标题,实现以及如何在viewController(不完整的代码)和本地html文件(可以与远程文件轻松交换)中使用它的示例。我已经调用了自定义协议:myapp://,您可以在底部的html文件中看到。

谢谢你的提问!我在很长一段时间里都在问这个问题,所以花时间计算出来的时间值得每一秒。

修改 如果有人在我目前的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>

答案 1 :(得分:39)

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(如果已分配,则应该已在某处释放)。该代码不能也不起作用。

其次,我建议在创建响应之前读取文件数据,并将[data length]作为预期内容长度而不是-1。

最后,-[NSURLProtocol stopLoading]不一定是错误,只是意味着你应该停止对响应的工作,如果可能的话。用户可能已将其取消。

答案 2 :(得分:2)

我希望我能正确理解你的问题:

1)加载远程网页...

2)使用app / build

中的文件替换某些远程资产

右?


嗯,我正在做的事情如下(由于Mobile Safari上的缓存限制为5MB,我将其用于视频,但我认为任何其他DOM内容应该同等工作):


•创建一个带有样式标签的本地(用Xcode编译)HTML页面,用于替换应用内/构建内容,设置为隐藏,例如: < / p>

<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 / etc ...使用根级别进行测试)放入项目并连接到目标


•具有

的远程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

答案 3 :(得分:1)

诀窍是为现有HTML提供显式基本URL。

将HTML加载到NSString中,使用UIWebView的loadHTMLString: baseURL:将URL中的URL作为基础。要将HTML加载到字符串中,可以使用[NSString stringWithContentsOfURL],但这是一种同步方法,并且在慢速连接时它会冻结设备。使用异步请求加载HTML也是可能的,但更多涉及。阅读NSURLConnection

答案 4 :(得分:0)

NSURLProtocol UIWebView 的不错选择,但直到现在 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]];