iOS WebView远程HTML与本地图像文件

以前也有过类似的问题,但我永远找不到解决方法。

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

[请注意,该html是远程的。 我总是得到从本地加载HTML和图像文件的答案 – 我已经完成了]

我得到的最接近的build议是在html页面和iOS应用程序中使用自定义urlscheme,例如myapp:/http://img.dovov.comimg.png,用NSURLProtocol子类截取myapp:// URL,并将图像replace为本地图片。 理论上听起来不错,但我还没有遇到一个完整的代码示例来说明这一点。

我有Java背景。 我可以使用自定义内容提供程序轻松地为Android执行此操作。 我相信iOS / Objective-C必须存在类似的解决scheme。 我没有足够的Objective-C经验在短时间内自己解决。

任何帮助将不胜感激。

好的,这里是一个例子,如何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> 

尼克·韦弗有正确的想法,但他的答案中的代码是行不通的。 它也打破了一些命名约定,永远不要使用NS前缀命名自己的类,并遵循大写字母缩略词(例如标识符名称中的URL)的约定。 为了使这容易遵循,我会坚持他/他的命名。

这些变化是微妙的,但重要的是:失去未分配的request伊娃,而是参考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]或者同名的属性来访问。 由于在他原来的代码ivar request永远不会被分配它总是nil (如果它分配它应该已经被释放的地方)。 该代码不能也不行。

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

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

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

1)加载远程网页…和

2)用app / build中的文件replace某些远程资产

对?


那么,我正在做的是如下(我使用它的video由于移动Safari 5MB的caching限制,但我认为任何其他的DOM内容应该同样工作):

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

 <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 …使用根级别进行testing)放入项目并连接到目标

远程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离线caching。; 希望这可以帮助。 F

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

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

NSURLProtocolUIWebView的一个不错的select,但直到现在, WKWebView仍然不支持它。 对于WKWebView我们可以build立一个本地的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]; 

指定本地文件的文件path时,添加本地服务器前缀:

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