NOTE: This technique works well with UIKit's UIWebView
and AppKit's WebView
, but does not work for the new WKWebView
, which appears to ignore the URL loading system. See comments.
Use NSURLProtocol
to handle local file reads as though they were remote requests. For a full example, see the one in PandoraBoy called ResourceURLProtocol
. I'll walk through a slightly simplified version of it here. We'll read http://.RESOURCE./path/to/file
as if it were <resources>/path/to/file
.
Every NSURLProtocol
will be asked if it can handle every request that comes up in the system. It need to answer whether it can in +canInitWithRequest:
. We'll say if the host is .RESOURCE.
, then we can handle it. .RESOURCE.
is an illegal DNS name, so it can't conflict with any real host (but it's a legal hostname for URL-purposes).
NSString *ResourceHost = @".RESOURCE.";
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return ( [[[request URL] scheme] isEqualToString:@"http"] &&
[[[request URL] host] isEqualToString:ResourceHost] );
}
Then we need a couple of bookkeeping methods. Nothing much to see here.
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
-(void)stopLoading {
return;
}
Now we get to the meat of it. startLoading
is where you're going to do whatever you want to do with the request.
-(void)startLoading {
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
NSString *notifierPath = [[thisBundle resourcePath] stringByAppendingPathComponent:[[[self request] URL] path]];
NSError *err;
NSData *data = [NSData dataWithContentsOfFile:notifierPath
options:NSUncachedRead // Assuming you only need to read this once
error:&err];
if( data != nil ) {
// Assuming you're only reading HTML.
// If you need other things, you'll need to work out the correct MIME type
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[[self request] URL]
MIMEType:@"text/html"
expectedContentLength:[data length]
textEncodingName:nil];
// And we just pass it to the caller
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
} else {
NSLog(@"BUG:Unable to load resource:%@:%@", notifierPath, [err description]);
[[self client] URLProtocol:self didFailWithError:err];
}
}
I also find a little ResourceURL
wrapper helpful:
@implementation ResourceURL
+ (ResourceURL*) resourceURLWithPath:(NSString *)path {
return [[[NSURL alloc] initWithScheme:@"http"
host:ResourceHost
path:path] autorelease];
}
@end
To use it, you just need to first register your protocol handler:
[NSURLProtocol registerClass:[ResourceURLProtocol class]];
Then you can create a "resource URL" and load it:
ResourceURL *resource = [ResourceURL resourceURLWithPath:...];
[webView loadRequest:[NSURLRequest requestWithURL:resource]];
For more details on NSURLProtocol
, as well as a more complicated caching example, see Drop-in Offline Caching for UIWebView (and NSURLProtocol).
PandoraBoy is full of NSURLProtocol
examples (look for all the classes with Protocol
in their names). You can hijack, spy on, redirect, or manipulate just about anything coming through the URL loading system this way.