As explained here, WKWebView
has a bug whereby apps that bundle a local webpage have to copy the bundle into the tmp
directory. My code for copying the bundle into tmp
is:
// Clear tmp directory
NSArray* temporaryDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in temporaryDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"build"];
NSString *temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"build"];
NSError *error = nil;
// Copy directory
if(![fileManager copyItemAtPath:sourcePath toPath:temporaryPath error:&error]) {
[self logError:@"Could not copy directory" :error];
}
I have timed this particular fragment of code, and it takes up 50% of my app's startup time! (~0.5s of a total of ~1s.)
Is there a way I can accelerate (or avoid completely) this particular fragment of code under iOS 8? Could threading help?
Apple is replacing UIWebView (and WebView) with WKWebView, an updated version, as UIWebView has been deprecated.
A WKWebView object is a platform-native view that you use to incorporate web content seamlessly into your app's UI. A web view supports a full web-browsing experience, and presents HTML, CSS, and JavaScript content alongside your app's native views.
Step-1: Firstly, create a new project with a Single View App. Step-2: Then, open “Main. storyboard” and on your ViewController's view drag “Webkit View”. Select a WKWebView and place it on your view of a view controller.
Given that your assets are large enough to take 0.5s to get copied, my suggestion would be to take a slightly different approach to quicken the (apparent) startup time:
div
s for loading the assets (if you like, you can include small assets (e.g. load indicator images) in base64)webView:didFinishNavigation:
), start the copy to the build folderevaluateJavaScript:completionHandler:
)While I realize this doesn't directly address the question, I think a slight change of approach would be best in this case.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With