Remote html iOS WebView with local image files

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

Here is my situation: my UIWebView loads the remote html page. Images used on web pages are known at build time. To speed up page loading, I want to pack image files into an iOS application and substitute them at runtime.

[Note that html has been deleted. I always get answers for downloading both html and image files from the local one - I already did it]

The closest recommendation I got was to use a special URL scheme like myapp: //images/img.png on the html page and in the iOS app, intercept the URL myapp: // with a subclass of NSURLProtocol and replace the image with local image. This sounds good in theory, but I have not come across a complete code example demonstrating this.

I have a Java background. I could make it easy for Android using a custom content provider. I am sure that a similar solution should exist for iOS / Objective-C. I do not have enough experience in Objective-C to solve it myself in the short amount of time that I have.

Any help would be appreciated.

+44
html iphone image uiwebview nsurlprotocol
Apr 6 '11 at 19:59
source share
5 answers

Ok, here is an example of a subclass of NSURLProtocol and deliver the image ( image1.png ) that is already in the kit. Below is the heading of the subclass, implementation, as well as an example of use in viewController (incomplete code) and a local html file (which can be easily exchanged with the remote). I called the user protocol: myapp:// , as you can see in the html file below.

And thanks for the question! I asked for it myself for quite a while, the time taken to figure it out was worth every second.

EDIT: If someone is having difficulty running my code under the current version of iOS, please take a look at the answer from sjs. When I answered the question, it worked, though. He pointed out some useful additions and fixed some problems, so he also provided him with details.

Here's what it looks like in my simulator:

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> 
+82
Apr 6 2018-11-21T00:
source share

Nick Weaver has the right idea, but the code in his answer doesn't work. It also violates some naming conventions, never call your own classes with the NS prefix and follow the convention of using abbreviations such as URLs in identifier names. I will stick with his naming in the interest of facilitating this.

The changes are subtle but important: lose the unassigned request ivar and instead refer to the actual request provided by NSURLProtocol and it works great.

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 

The problem with Nick code is that NSURLProtocol subclasses NSURLProtocol n't need to save the request. NSURLProtocol already has a request, and you can access it using the method -[NSURLProtocol request] or a property with the same name. Since request ivar in its source code is never assigned, it is always nil (and if it was assigned, it should have been released somewhere). This code cannot and does not work.

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

And finally, -[NSURLProtocol stopLoading] not necessarily an error, it just means that you should stop working on the answer, if possible. User can cancel it.

+39
Nov 27 '11 at 19:29
source share

Hope I understand your problem correctly:

1) upload a remote web page ... and

2) replace some remote resources with files in the / build application

Right?




Good thing I do as follows (I use it for videos because of the 5 MB cache limit on Mobile Safari, but I think that any other DOM content should work the same way):


• create a local (in order to compile using Xcode) HTML-page with style tags, so that the in-app / build subtitle is replaced with a hidden one, for example:

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


• the contents of the div are supplied in the same file, for example

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


• (using jQuery here) downloads the actual content from the remote server and add the local (imported Xcode object) to your target div, for example

 <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 www files (index.html / jquery.js / etc ... use the root levels for testing) into the project and connect to the target


• remote HTML file (here is located at yourserver.com/index-test.html) with

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


as well as the destination separator, e.g.

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


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

 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 for me, best when combined with https://github.com/rnapier/RNCachingURLProtocol for offline caching. Hope this helps. F

+2
Jun 05 '14 at 8:36
source share

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

Load HTML into NSString, use UIWebView loadHTMLString: baseURL: with the URL in your kit as a base. You can use [NSString stringWithContentsOfURL] to load HTML into a string, but this is a synchronous method, and when connected slowly, it will freeze the device. Using an async request to load HTML is also possible, but more active. Read on NSURLConnection .

+1
Apr 6 2018-11-11T00:
source share

NSURLProtocol is a good choice for UIWebView , but so far WKWebView still does not support it. For WKWebView, we can create a local HTTP server to process the request for a local file, GCDWebServer is suitable for this

 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 specifying the path to 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]]; 
0
Mar 22 '16 at 4:22
source share



All Articles