41

In my Swift iOS app, I want to download some dynamic HTML pages from a remote server, save them in the document directory, and display those pages from document directory.

I was using this to load the page:

var appWebView:WKWebView? ... appWebView!.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: htmlPath))) 

Everything works on the simulator, but when I moved to real phones, it just showed a blank page. I then connected to the app using Safari, and found it complained with "Failed to load resource".

I then tried to first read the content of the page at htmlPath, then use

appWebView!.loadHTMLString() 

to load the page. It works when the HTML page is simple. But if the HTML references something else, i.e. a JavaScript file also in the document directory (with an absolute path like <script src="file:////var/mobile/Containers/Data/Application/762035C9-2BF2-4CDD-B5B1-574A0E2B0728/Documents/xxxxx.js">), it will fail to load.

Does anyone know why this happens, and how to resolve the issue?

More info:

  • XCode version: 7.3.1
  • Deployment Target: 8.1 (I tried to use 9.3 too, but that didn't help.)
4
  • Did you check this? Commented Sep 9, 2016 at 22:51
  • Thank you!! I will try that.. Commented Sep 9, 2016 at 23:19
  • Please paste your download / saving html content code Commented Sep 12, 2016 at 13:26
  • From my experience - WKWebView has loading issues when the webview is detached from the view hierarchy. This could be your issue. Also, you could try to following API for file requests: func loadFileURL(_ URL: URL, allowingReadAccessTo readAccessURL: URL) -> WKNavigation? [developer.apple.com/reference/webkit/wkwebview/… Commented Sep 20, 2016 at 12:31

8 Answers 8

42
+25

This is a simplified version of what I have used to load local files in a project of mine (iOS 10, Swift 3). I have just updated my code (7.5.2017) after testing it out again on iOS 10.3.1 and iPhone 7+ as requested by Raghuram and Fox5150 in the comments.

I just created a completely new project and this is the folder structure:enter image description here

Update 19.04.2018: Added a new feature to download a .zip with HTML, CSS, JS files, unzip it in /Documents/ (Alamofire + Zip) and then load those files into the webView. You can find it in the GitHub sample project as well. Again, feel free to fork & star! :)

Update 08.02.2018: finally added a GitHub sample project, which also includes a local JavaScript file. Feel free to fork & star! :)

Version 1 with webView.loadFileURL()

ViewController.swift

import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { override func viewDidLoad() { super.viewDidLoad() let webView = WKWebView() let htmlPath = Bundle.main.path(forResource: "index", ofType: "html") let htmlUrl = URL(fileURLWithPath: htmlPath!, isDirectory: false) webView.loadFileURL(htmlUrl, allowingReadAccessTo: htmlUrl) webView.navigationDelegate = self view = webView } } 

Version 2 with webView.loadHTMLString()

ViewController.swift

import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { override func viewDidLoad() { super.viewDidLoad() let webView = WKWebView() let htmlPath = Bundle.main.path(forResource: "index", ofType: "html") let folderPath = Bundle.main.bundlePath let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true) do { let htmlString = try NSString(contentsOfFile: htmlPath!, encoding: String.Encoding.utf8.rawValue) webView.loadHTMLString(htmlString as String, baseURL: baseUrl) } catch { // catch error } webView.navigationDelegate = self view = webView } } 

Gotchas to look out for:

  • Make sure that your local html/js/css files are in Project -> Target -> Build Phases -> Copy Bundle Resources
  • Make sure that your html files don't reference relative paths e.g. css/styles.css because iOS will flatten your file structure and styles.css will be on the same level as index.html so write <link rel="stylesheet" type="text/css" href="styles.css"> instead

Given the 2 versions and the gotchas here are my html/css files from the project:

web/index.html

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Offline WebKit</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <h1 id="webkit-h1">Offline WebKit!</h1> </body> </html> 

web/css/styles.css

#webkit-h1 { font-size: 80px; color: lightblue; } 

If somebody wants a GitHub sample project, tell me in the comments section and I'll upload it.

Sign up to request clarification or add additional context in comments.

19 Comments

So looks like i might missed the "baseURL" argument when calling loadHTMLString. But I like what Roman pointed out better: (stackoverflow.com/questions/24882834/…). I actually do have a <base> tag in my html page, which is referencing a remote server (as the file contain some javascript calling the remote server but was not using absolute path, and I don't want to go over the whole javascript file to update all those calls), at the same time, the html also are using some javascript and css files on the local drive.
I am getting this error: unexpectedly found nil while unwrapping an Optional value
@tech4242 Great answer! You can also support sub-folder references in your html / css / js, by adding your assets using folder references. You can just drag your html assets folder in your project structure in Xcode and select "Create folder references". You then have access to your entire assets directory structure by doing the following: (let's say your folder is called HtmlAssets) Bundle.main.url(forResource: "HtmlAssets", withExtension: nil).
Version 1 is working with WKWebView on macOS. Version 2 doesn't work for me with WKWebView on macOS though. Seems that the allowingReadAccessTo param may be the key to unlocking access to the application on macOS to the baseURL path.
@tech4242 Above example works well, but if I add javascript file same as styles.css it's not loading in html.
|
16

Swift 4 Method

This method allows WKWebView to properly read your hierarchy of directories and sub-directories for linked CSS/JS files. You do NOT need to change your HTML, CSS or JS code.

Updated for Xcode 9.3

Step 1

Import the folder of local web files anywhere into your project. Make sure that you:

Xcode > File > Add Files to "Project"

☑️ Copy items if needed

☑️ Create folder references (not "Create groups")

☑️ Add to targets

Step 2

Go to the View Controller with the WKWebView and add the following code to the viewDidLoad method:

let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "website")! webView.loadFileURL(url, allowingReadAccessTo: url) let request = URLRequest(url: url) webView.load(request) 
  • index – the name of the file to load (without the .html extension)
  • website – the name of your web folder (index.html should be at the root of this directory)

Conclusion

The overall code should look something like this:

import UIKit import WebKit class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { @IBOutlet weak var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() webView.uiDelegate = self webView.navigationDelegate = self let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "Website")! webView.loadFileURL(url, allowingReadAccessTo: url) let request = URLRequest(url: url) webView.load(request) } } 

If any of you have further questions about this method or the code, I'll do my best to answer. :)

2 Comments

If you allow me, just one quick question: why do you use webView.loadFileURL then + webView.load later? Just the loadFileURL doesn't work?
that's a mistake. you don't want both.
3

This solution helped me:

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

3 Comments

will apple accept application for appstore if we use this?
i am facing the issue i used this and now it is loading files
im using xcode 9.4.1, it works in the latest version but im having trouble making it work in ios 9.0
1

This works well (Swift 3, Xcode 8):

import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! override func loadView() { webView = WKWebView() webView.navigationDelegate = self view = webView } override func viewDidLoad() { super.viewDidLoad() if let url = Bundle.main.url(forResource: "file", withExtension: "txt") { do { let contents = try String(contentsOfFile: url.path) webView.loadHTMLString(contents, baseURL: url.deletingLastPathComponent()) } catch { print("Could not load the HTML string.") } } } } 

Comments

1

This works nicely with file URL or remote URL, and whether file is in the bundle or in documents:

if url.isFileURL { webView.loadFileURL(url, allowingReadAccessTo: url) } else { let request = URLRequest(url: url) webView.load(request) } 

Comments

1

Constructing the URLs this way allowed me to load resources from the document directory with WKWebView:

guard let docDir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else { return } let resourceURL = docDir.appendingPathComponent("/Path/To/Your/Resource") self.wkWebView.loadFileURL(resourceURL, allowingReadAccessTo: docDir) 

1 Comment

with ur above solution I am perfectly able to load one single image into WKWebView from doc dir , but can you help me out I have an html which contains the. last path of the images and I have already downloaded the images in documents directory how can show that html images in WKWEBview
0

The files must be in the document directory.

I implemented the following to retrieve a document:

let documentDirUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let fileNameWithExtension = "IMG_0002.PNG" let indexFileUrl = documentDirUrl.appendingPathComponent(fileNameWithExtension) if FileManager.default.fileExists(atPath: indexFileUrl.path) { webView.loadFileURL(indexFileUrl, allowingReadAccessTo: documentDirUrl) } 

Comments

-1

Check that ticket: iOS: How to load local files (not in the bundle) in a WKWebView?

var nsurl = URL(fileURLWithPath: URL(fileURLWithPath: URL(fileURLWithPath: documentsDirectory()).appendingPathComponent(user_appli).absoluteString).appendingPathComponent("index.html").absoluteString) //locally var readAccessToURL: URL? = nsurl.deletingLastPathComponent?.deletingLastPathComponent if let anURL = readAccessToURL { webView?.loadFileURL(nsurl, allowingReadAccessTo: anURL) } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.