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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5572258/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 07:49:55  来源:igfitidea点击:

iOS WebView remote html with local image files

iphonehtmlimageuiwebviewnsurlprotocol

提问by CM Subram

Similar questions have been asked before, but I could never find a solution.

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

Here is my situation - my UIWebView loads a remote html page. The images used in the web pages are known at build time. In order to make the page load faster, I want to package the image files in the iOS application and substitue them at runtime.

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

[Please note that the html is remote. I always get answers for loading both html and image files from local - I have done that already]

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

The closest recommendation I got was to use a custom url scheme such as myapp://images/img.png in the html page and in the iOS application, intercept the myapp:// URL with NSURLProtocol subclass and replace the image with a local image. Sounded good in theory, but I haven't come across a complete code example demonstrating this.

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

I have Java background. I could do this easily for Android using a Custom Content Provider. I am sure a similar solution must exist for iOS/Objective-C. I don't have enough experience in Objective-C to solve it myself in the short timeframe I have.

我有 Java 背景。我可以使用自定义内容提供程序为 Android 轻松完成此操作。我确信 iOS/Objective-C 必须存在类似的解决方案。我没有足够的 Objective-C 经验,无法在短时间内自行解决。

Any help will be appreciated.

任何帮助将不胜感激。

回答by Nick Weaver

Ok here is an example how to subclass NSURLProtocoland deliver an image (image1.png) which is already in the bundle. Below is the subclasses' header, the implementation as well as an example how to use it in a viewController(incomplete code) and a local html file(which can be easily exchanged with a remote one). I've called the custom protocol: myapp://as you can see in the html file at the bottom.

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

And thanks for the question! I was asking this myself for quite a long time, the time it took to figure this out was worth every second.

并感谢您的提问!我自己问了很长时间,弄清楚这一点所花费的时间每一秒都是值得的。

EDIT:If someone has difficulties making my code run under the current iOS version, please have a look at the answer from sjs. When I answered the question it was working though. He's pointing out some helpful additions and corrected some issues, so give props to him as well.

编辑:如果有人在当前的 iOS 版本下运行我的代码有困难,请查看 sjs 的答案。当我回答这个问题时,它虽然有效。他指出了一些有用的补充并纠正了一些问题,所以也给他一些道具。

This is how it looks in my simulator:

这是它在我的模拟器中的样子:

enter image description here

在此处输入图片说明

MyCustomURLProtocol.h

MyCustomURLProtocol.h

@interface MyCustomURLProtocol : NSURLProtocol
{
    NSURLRequest *request;
}

@property (nonatomic, retain) NSURLRequest *request;

@end

MyCustomURLProtocol.m

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

MyCustomProtocolViewController.h

@interface MyCustomProtocolViewController : UIViewController {
    UIWebView *webView;
}

@property (nonatomic, retain) UIWebView *webView;

@end

MyCustomProtocolViewController.m

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

<html>
<body>
    <h1>we are loading a custom protocol</h1>
    <b>image?</b><br/>
    <img src="myapp://image1.png" />
<body>
</html>

回答by Sami Samhuri

Nick Weaver has the right idea but the code in his answer does not work. It breaks some naming conventions as well, never name your own classes with the NSprefix, and follow the convention of capitalizing acronyms such as URL in identifier names. I'll stick w/ his naming in the interest of making this easy to follow.

Nick Weaver 有正确的想法,但他的答案中的代码不起作用。它也打破了一些命名约定,永远不要用NS前缀命名你自己的类,并遵循大写首字母缩写词的约定,例如标识符名称中的 URL。我会坚持使用他的名字,以使这更容易理解。

The changes are subtle but important: lose the unassigned requestivar and instead refer to the the actual request provided by NSURLProtocoland it works fine.

这些变化很微妙但很重要:丢失未分配的requestivar,而是参考 提供的实际请求NSURLProtocol,它工作正常。

NSURLProtocolCustom.h

NSURLProtocolCustom.h

@interface NSURLProtocolCustom : NSURLProtocol
@end

NSURLProtocolCustom.m

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

The problem with Nick's code is that subclasses of NSURLProtocoldo not need to store the request. NSURLProtocolalready has the request and you can access with the method -[NSURLProtocol request]or the property of the same name. Since the requestivar in his original code is never assigned it is always nil(and if it was assigned it should have been released somewhere). That code cannot and does not work.

Nick 代码的问题在于 的子类NSURLProtocol不需要存储请求。NSURLProtocol已经有请求,您可以使用-[NSURLProtocol request]同名的方法或属性进行访问。由于request他的原始代码中的ivar 从未被分配过,所以它总是nil(如果它被分配了,它应该在某个地方发布)。该代码不能也不起作用。

Second, I recommend reading the file data before creating the response and passing [data length]as the expected content length instead of -1.

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

And finally, -[NSURLProtocol stopLoading]is not necessarily an error, it just means you should stop work on a response, if possible. The user may have cancelled it.

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

回答by GermanGangsta

I hope I am understanding your problem correctly:

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

1) load a remote webpage ... and

1)加载远程网页......和

2) substitute certain remote assets with files within the app/build

2)用应用程序/构建中的文件替换某些远程资产

Right?

对?



Well, what I am doing is as follows (I use it for videos due to the caching limit of 5MB on Mobile Safari, but I think any other DOM content should work equally):

好吧,我正在做的事情如下(由于 Mobile Safari 上的缓存限制为 5MB,我将它用于视频,但我认为任何其他 DOM 内容都应该同样有效):


? create a local (to be compiled with Xcode) HTML page with style tags, for the in-app/build content to be substituted, set to hidden, e.g.:


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

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


? in the same file supply a content div, e.g.


? 在同一个文件中提供一个内容 div,例如

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


? (using jQuery here) load the actual content from the remote server and append your local (Xcode imported asset) to your target div, e.g.


? (在此处使用 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>


? drop the www files (index.html / jquery.js / etc ... use root levels for testing) into the project and connect to target


? 将 www 文件(index.html / jquery.js / etc ... 使用根级别进行测试)放入项目并连接到目标


? the remote HTML file (here located at yourserver.com/index-test.html) having a


? 远程 HTML 文件(此处位于 yourserver.com/index-test.html)具有

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


? as well as a destination div, e.g.


? 以及目标div,例如

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


? and finally in your Xcode project, load the local HTML into the web view


? 最后在您的 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];


Works a treat for me, best in conjunction with https://github.com/rnapier/RNCachingURLProtocol, for offline caching. Hope this helps. F

对我来说是一种享受,最好与https://github.com/rnapier/RNCachingURLProtocol结合使用,用于离线缓存。希望这可以帮助。F

回答by Seva Alekseyev

The trick is to provide the explicit base URL to an existing HTML.

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

Load the HTML into a NSString, use UIWebView's loadHTMLString: baseURL:with the URL into your bundle as the base. For loading HTML into a string, you can use [NSString stringWithContentsOfURL], but that's a synchronous method, and on slow connection it will freeze the device. Using an async request to load the HTML is also possible, but more involved. Read up on NSURLConnection.

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

回答by Albert Zhang

NSURLProtocolis a good choice for UIWebView, but until now the WKWebViewstill not support it. For WKWebViewwe can build a local HTTP server to handle the local file request, the GCDWebServeris good for this:

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

When specify the file path of the local file, add the local server prefix:

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

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