WKWebView not loading local files under iOS 8

84,204

Solution 1

They finally solved the bug! Now we can use -[WKWebView loadFileURL:allowingReadAccessToURL:]. Apparently the fix was worth some seconds in WWDC 2015 video 504 Introducing Safari View Controller

https://developer.apple.com/videos/wwdc/2015/?id=504

For iOS8 ~ iOS10 (Swift 3)

As Dan Fabulish's answer states this is a bug of WKWebView which apparently is not being solved any time soon and as he said there is a work-around :)

I am answering just because I wanted to show the work-around here. IMO code shown in https://github.com/shazron/WKWebViewFIleUrlTest is full of unrelated details most people are probably not interested in.

The work-around is 20 lines of code, error handling and comments included, no need of a server :)

func fileURLForBuggyWKWebView8(fileURL: URL) throws -> URL {
    // Some safety checks
    if !fileURL.isFileURL {
        throw NSError(
            domain: "BuggyWKWebViewDomain",
            code: 1001,
            userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("URL must be a file URL.", comment:"")])
    }
    try! fileURL.checkResourceIsReachable()

    // Create "/temp/www" directory
    let fm = FileManager.default
    let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("www")
    try! fm.createDirectory(at: tmpDirURL, withIntermediateDirectories: true, attributes: nil)

    // Now copy given file to the temp directory
    let dstURL = tmpDirURL.appendingPathComponent(fileURL.lastPathComponent)
    let _ = try? fm.removeItem(at: dstURL)
    try! fm.copyItem(at: fileURL, to: dstURL)

    // Files in "/temp/www" load flawlesly :)
    return dstURL
}

And can be used as:

override func viewDidLoad() {
    super.viewDidLoad()
    var fileURL = URL(fileURLWithPath: Bundle.main.path(forResource:"file", ofType: "pdf")!)

    if #available(iOS 9.0, *) {
        // iOS9 and above. One year later things are OK.
        webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
    } else {
        // iOS8. Things can (sometimes) be workaround-ed
        //   Brave people can do just this
        //   fileURL = try! pathForBuggyWKWebView8(fileURL: fileURL)
        //   webView.load(URLRequest(url: fileURL))
        do {
            fileURL = try fileURLForBuggyWKWebView8(fileURL: fileURL)
            webView.load(URLRequest(url: fileURL))
        } catch let error as NSError {
            print("Error: " + error.debugDescription)
        }
    }
}

Solution 2

WKWebView can't load content from file: URLs via its loadRequest: method. http://www.openradar.me/18039024

You can load content via loadHTMLString:, but if your baseURL is a file: URL, then it still won't work.

iOS 9 has a new API that will do what you want, [WKWebView loadFileURL:allowingReadAccessToURL:].

There is a workaround for iOS 8, demonstrated by shazron in Objective-C here https://github.com/shazron/WKWebViewFIleUrlTest to copy files into /tmp/www and load them from there.

If you're working in Swift, you could try nachos4d's sample instead. (It's also much shorter than shazron's sample, so if you're having trouble with shazron's code, give that a try instead.)

Solution 3

An example of how to use [WKWebView loadFileURL:allowingReadAccessToURL:] on iOS 9.

When you are moving the web folder to a project, select "Create folder references"

enter image description here

Then use code that is something like this(Swift 2):

if let filePath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp/index.html"){
  let url = NSURL(fileURLWithPath: filePath)
  if let webAppPath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp") {
    let webAppUrl = NSURL(fileURLWithPath: webAppPath, isDirectory: true)
    webView.loadFileURL(url, allowingReadAccessToURL: webAppUrl)
  }
}

In the html file use filepaths like this

<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">

not like this

<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">

An example of directory that is moved to a xcode project.

enter image description here

Solution 4

Temporary workaround: I'm using GCDWebServer, as suggested by GuidoMB.

I first find the path of my bundled "www/" folder (which contains an "index.html"):

NSString *docRoot = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"www"].stringByDeletingLastPathComponent;

... then start it up like so:

_webServer = [[GCDWebServer alloc] init];
[_webServer addGETHandlerForBasePath:@"/" directoryPath:docRoot indexFilename:@"index.html" cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:port bonjourName:nil];

To stop it:

[_webServer stop];
_webServer = nil;

Performance appears fine, even on an iPad 2.


I did notice a crash after the app goes into the background, so I stop it on applicationDidEnterBackground: and applicationWillTerminate:; I start/restart it on application:didFinishLaunching... and applicationWillEnterForeground:.

Solution 5

[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

This solved the problem for me iOS 8.0+ dev.apple.com

also this seems to worked just fine too...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
Share:
84,204
Lim Thye Chean
Author by

Lim Thye Chean

Virtual GS studio develops and publishes multimedia eBooks, retro games and children applications for iOS and Android devices.

Updated on May 19, 2020

Comments

  • Lim Thye Chean
    Lim Thye Chean about 4 years

    For previous iOS 8 betas, load a local web app (in Bundle) and it works fine for both UIWebView and WKWebView, and I even ported a web game using the new WKWebView API.

    var url = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("car", ofType:"html"))
    
    webView = WKWebView(frame:view.frame)
    webView!.loadRequest(NSURLRequest(URL:url))
    
    view.addSubview(webView)
    

    But in beta 4, I just got a blank white screen (UIWebView still work), looks like nothing is loaded or executed. I saw an error in the log:

    Could not create a sandbox extension for /

    Any help to guide me to the right direction? Thanks!

  • Lim Thye Chean
    Lim Thye Chean almost 10 years
    Thanks I will try it out.
  • Lim Thye Chean
    Lim Thye Chean almost 10 years
    This seems to work only for the HTML files but not other resources.
  • Lim Thye Chean
    Lim Thye Chean almost 10 years
    This only works for HTML file itself not the resources, right?
  • Oleksii
    Oleksii almost 10 years
    Unfortunately yes, only the HTML file seems to be loading. Let's hope that it's just a bug, and not new restriction to load local files. I'm trying to find this bug in the WebKit sources.
  • mszaro
    mszaro almost 10 years
    I've run into the same issue - HTML files are loaded but images and other resources that are on the local filesystem are not loaded. In the console, WebKit is throwing an error "not allowed to load local resource". I filed a bug for this, radar# 17835098
  • Mike M
    Mike M over 9 years
    One workaround (mentioned here: devforums.apple.com/message/1051027) is to move content into tmp and access it from there. My quick test seems to indicate that that it does work...
  • invalidArgument
    invalidArgument over 9 years
    Hi @Dan. I downloaded your example from git. Thank you for that. I seem to get a valid URL, with all needed files copied to a new folder in /Documents. But the webview now tries to load the page forever. Any clue? I had already posted a question here: stackoverflow.com/q/26455432/873436, if you prefer to answer there...
  • andrew k
    andrew k over 9 years
    Using a "server" probably eats up any performance benefits that WKWebView provides over UIWebView. Might as well stick with the old API until this is fixed.
  • EthanB
    EthanB over 9 years
    @ray Not with a Single-Page App.
  • xhg
    xhg over 9 years
    I tried moving to /tmp, but seems still blank, anybody tried and succeed?
  • geppy
    geppy over 9 years
    Does the /tmp/ workaround work on 8.0? It's not working for me.
  • Greg Maletic
    Greg Maletic over 9 years
    Moving the files to /tmp works for me. But...my god, how did this get through testing?
  • Tom Hamming
    Tom Hamming over 9 years
    I've gotten GCDWebServer to work too, in internal builds of my app. And if there's a lot of javascript in your app, the server is totally worth it. But there are other issues that prevent me from using WKWebView right now, so I'm hoping for improvements in iOS 9.
  • nacho4d
    nacho4d over 9 years
    That sample is WAY TOO MUCH code just for a demo. The file to be read must be inside /tmp/www/ . Use NSTemporaryDirectory() and NSFileManager to create www directory (because there is no such directory by default). Then copy your file in there and now read this file :)
  • Dan Fabulich
    Dan Fabulich over 9 years
    @nacho4d If you prepare a simpler demo, I'll link to that instead.
  • AwDogsGo2Heaven
    AwDogsGo2Heaven over 9 years
    I'm guessing this works because maybe with normal remote websites its downloading the files into the TMP directory anyways, right? So dumping your files there makes it treat it as if it did download that. It sucks to have to copy all those files, but I guess it only has to happen once during start up. If you did it this way too, it wouldn't be too hard to switch things out when this bug is fixed, as the only thing that would change is not copying the files, and perhaps a different api call based on the description of the wkwebkit fix.
  • Stefan Arentz
    Stefan Arentz over 9 years
    Is it safe to store data in the NSTemporaryDirectory() directory? What happens if the device runs low on space? Will iOS remove files from the temporary directories?
  • nacho4d
    nacho4d over 9 years
    As its name suggests it is temporary. So before showing something you should check it exists. The OS wipes that directory from time to time.
  • chipbk10
    chipbk10 about 9 years
    And how to show it on a WebView? I really don't understand what GCDWebServer is for?
  • EthanB
    EthanB about 9 years
    Instead of pointing the webview to "file://.....", you point it to "http ://localhost:<port>/...".
  • ninjaneer
    ninjaneer about 9 years
    8.3 and it's still not fixed...?
  • Steven Fisher
    Steven Fisher about 9 years
    This sounds more like a choice than a bug to me.
  • chmaynard
    chmaynard about 9 years
    I decided to use XWebView after looking at other work-arounds for this problem. XWebView is a framework implemented in Swift but I had no problem using it in my iOS 8 Objective-C app.
  • Karol Klepacki
    Karol Klepacki about 9 years
    Seems to be fixed in 9.0
  • Piwaf
    Piwaf almost 9 years
    Some modifications for it to copy the entire folder, images and all. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
  • user3246173
    user3246173 almost 9 years
    I can't get this to work on iOS 8 in simulator. I want to put an .png in /tmp/www and then use img tag in my html. What should I use for the img tag src?
  • Patrick Fabrizius
    Patrick Fabrizius almost 9 years
    Piwaf's solution worked slightly better for me, note that it should be "self.webView" and not "self.loadingWebView" given the example above though
  • jvoll
    jvoll almost 9 years
    Does this workaround work on the simulator? It seems to be failing on the copyItemAtPath. Perhaps I can't write to that directory on the simulator?
  • nacho4d
    nacho4d almost 9 years
    In the simulator you don't this workaround. You can place an #ifdef inside pathForBuggyWKWebView and return filePath without doing anything :)
  • jvoll
    jvoll almost 9 years
    Thanks @nacho4d. tldr; the /tmp folder solution will not work on 8.0 but will on 8.0.2. I was having trouble getting this solution to work on my test device and eventually cloned shazron's repo to give it a try. That didn't work either. Turns out that shazron's solution doesn't work on the version of iOS my device was running 8.0 (12A366) on iPhone 6 Plus. I tried it on a device (iPad Mini) running iOS 8.0.2 and it works fine.
  • Tinkerbell
    Tinkerbell almost 9 years
    was exactly what I needed. Thanks!
  • SleepNot
    SleepNot over 8 years
    Is there a delegate method like webViewDidFinishLoad for loadFileURL:allowingReadAccessToURL:?
  • DàChún
    DàChún over 8 years
    it should be let _ = try? fm.removeItemAtURL(dstURL) instead of let _ = try? fileMgr.removeItemAtURL(dstURL)
  • nacho4d
    nacho4d over 8 years
    Thanks, I just fixed it!
  • User1
    User1 over 8 years
    @KarolKlepacki are you sure? From the bug comments it seems to suggest it is only partially fixed? http://www.openradar.me/18039024#ag9zfm9wZW5yYWRhci1ocmRyFAs‌​SB0NvbW1lbnQYgICA4LO‌​39AoM
  • jenson-button-event
    jenson-button-event over 8 years
    Only for simple websites. If you are using ajax or loading local views via angular, expect "Cross origin requests are only supported for HTTP". Your only fallback is the local webserver approach which i do not like since it is visible on the local network. This needs noting the in the post, save folk some hours.
  • TruMan1
    TruMan1 about 8 years
    What if the index.html needs to be dynamically generated, such as being a template with placeholders then reused for different content records? I guess I'm forced to use loadHTMLString, but then local css and js files stop working?
  • Rodrigo Lima
    Rodrigo Lima about 8 years
    @jenson-button-event have you found any other solutions to deal with CORS issues with local files on iOS 9+? As you mentioned, loadFileURL:allowReadAccess does not seem to help in those cases.
  • jenson-button-event
    jenson-button-event about 8 years
    @RodrigoLima - no - went with the local web server approach. had no issues with it. I just don't like the fact the server is publicly visible.
  • nullqube
    nullqube almost 7 years
    instead of FILE you can put DIR too .
  • plivesey
    plivesey over 6 years
    This is a great find. I don't know why it isn't voted higher.
  • plivesey
    plivesey over 6 years
    This post has more info (including links to webkit source): stackoverflow.com/questions/36013645/…
  • Grigory Entin
    Grigory Entin over 5 years
    For those wondering why their -[WKWebView loadFileURL:allowingReadAccessToURL:] doesn't work: make sure to check you give it "standard" file URL. I could not get it working with URL built with URL(string: path, relativeTo: fileURL). (And yes, isFileURL returned true for it/it worked in Simulator but not on the device). Applying .standardizedFileURL to the URL solved the issue for me.
  • shahil
    shahil over 5 years
    in the swift version, please add a semicolon before base64. result += "data:image/" + attachmentType + ";base64," + base64String
  • Żabojad
    Żabojad over 4 years
    @GrigoryEntin I have a very similar issue but standardizedURL doesn't help :(. more details there: stackoverflow.com/questions/58437328/…
  • Vignesh Kumar
    Vignesh Kumar over 4 years
    configuration.preferences setValue will give crash on iOS 9.3
  • hotdogsoup.nl
    hotdogsoup.nl over 4 years
    Thanks, this is the only thing that worked for me, because I download a .gif file to a temp folder on the device, and then load that file into the WKWebView as an <img> within a HTMLstring.
  • arlomedia
    arlomedia about 4 years
    I'm using the iOS 13 SDK but trying to keep compatibility with iOS 8, and the allowFileAccessFromFileURLs approach crashes with NSUnknownKeyException.
  • Curtis
    Curtis about 4 years
    this worked for me only if I replaced "productURL" with "FILE_PATH"
  • iMinion
    iMinion almost 4 years
    @nacho4d the above solution does not work for ios13 if u could help me out stackoverflow.com/questions/63800637/…
  • Dipesh Pokhrel
    Dipesh Pokhrel over 3 years
    @patrickd105 , I have some doubt regarding the result variable, and can you post the code with the calling and caller function so that everything become clear
  • famfamfam
    famfamfam over 2 years
    i have problem when inject bootstrapt file, please help stackoverflow.com/questions/71421255/…