以前曾提出过类似的问题,但我找不到解决办法。
这是我的情况 - 我的UIWebView加载了一个远程html页面。网页中使用的图像在构建时是已知的。为了加快页面加载速度,我想将图像文件打包到iOS应用程序中并在运行时替换它们。
[请注意,html是远程的。我总是得到从本地加载html和图像文件的答案 - 我已经这样做了]
我得到的最接近的建议是在html页面和iOS应用程序中使用自定义URL方案,例如myapp://images/img.png,使用NSURLProtocol子类截取myapp:// URL并替换图像与当地的形象。在理论上听起来不错,但我没有遇到一个完整的代码示例来证明这一点。
我有Java背景。我可以使用自定义内容提供商轻松地为Android做到这一点。我相信iOS / Objective-C必须存在类似的解决方案。我没有足够的Objective-C经验可以在很短的时间内自己解决它。
任何帮助将不胜感激。
答案 0 :(得分:84)
好的这里是一个如何子类化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>
答案 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 ...使用根级别进行测试)放入项目并连接到目标
•具有
<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)
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]];