56

Apple's recommendation:

In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView.

Thus, I have replaced my good old UIWebView with a shiny new WKWebView. But what I thought to be an easy exercise (simply swapping the classes and replacing the delegate methods) turned out to be a real mess.

The Problem

When I load an HTML string using

loadHTMLString(String, baseURL: URL?)

the web view loads and renders the pure HTML but it doesn't load any images or CSS files referenced inside the htmlString.

This happens only on a real device!
In Simultor all referenced resources are loaded correctly.

Simulator Real Device

Example

I have defined a simple htmlString in my view controller class:

let imageName = "image.png"

let libraryURL: URL // The default Library URL

var htmlString: String {
    return "<html> ... <img src=\"\(imageName)\" /> ... </html>"
    // "..." represents more valid HTML code incl. header and body tags
}

The image is stored in the root Library folder so its URL is:

let imageURL = libraryURL.appendingPathComponent(imageName)

Now I load the htmlString into the web view:

webView.loadHTMLString(htmlString, baseURL: libraryURL)

and it doesn't load the image even though the baseURL is set correctly.

Ideas for a Solution

  1. Maybe WKWebView has a problem with resolving relative paths so my first idea was to use absolute paths inside the HTML string instead.
    → ❌ Doesn't work.

  2. Two answers to another SO post suggested that using
    loadFileURL(URL, allowingReadAccessTo: URL)
    instead of loadHTMLString(...) works in iOS 9+.
    → ✅ That works.

However, I cannot use solution 2 because my HTML files are encrypted and the decrypted files must not be stored on the disk.

Question

Is there any way to load local resources like images and styles using the WKWebView's

loadHTMLString(String, baseURL: URL?)

function? Or is still a bug in iOS 9+?

(I just cannot believe that Apple provides and recommends using a web view that cannot load any local web content from inside an HTML string?!)

Community
  • 1
  • 1
Mischa
  • 15,816
  • 8
  • 59
  • 117
  • Try this: https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http – vijay Jul 19 '17 at 07:12
  • Try this: https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http – vijay Jul 19 '17 at 07:13
  • I am having the same issue. Tried with "loadFileURL(URL, allowingReadAccessTo: URL)". It is still not working for me. Images are still not loading for me. How did you get it to work? – Vidhya Sri Oct 04 '18 at 10:57
  • 2
    How did you get it to work? – ozd Feb 01 '20 at 12:34
  • It is 3 years and 8 month later and yet I am still stuck with the very same issue – Solomiya Jun 09 '20 at 17:02
  • @Solomiya Well, WKWebView is quite a thing... ‍♂️ – Mischa Jun 09 '20 at 17:17
  • @Mischa so, did you succeed to finally solve this issue? I can see no answer was accepted. Could you share the approach that actually helped you? – Solomiya Jun 10 '20 at 08:34
  • Honestly, I don’t remember exactly. I think we found some workaround or used a different approach altogether. But I think I never really got an answer why this doesn’t work. Otherwise I would have answered my own question here... – Mischa Jun 10 '20 at 09:03
  • @Solomiya did u find any workable solution to that ? Can you help me in a similar problem https://stackoverflow.com/questions/63800637/unable-to-load-html-with-only-images-in-wkwebview-ios-13-swift-5 – iMinion Sep 08 '20 at 20:48

12 Answers12

16

Without taking a look at your actual project it's difficult to give some hundreed percent sure advices.

However:

class ViewController: UIViewController {

    var webView = WKWebView()

    override func viewDidLoad() {
        super.viewDidLoad()
        webView.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "webView" : webView
        ]
        view.addSubview(webView)
        var constraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[webView]|", options: [.AlignAllLeading, .AlignAllTrailing], metrics: nil, views: views)
        constraints.appendContentsOf(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: [.AlignAllTop, .AlignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activateConstraints(constraints)

        let path = NSBundle.mainBundle().pathForResource("ios - WKWebView fails to load images and CSS using loadHTMLString(_, baseURL_) - Stack Overflow", ofType: "htm")
        let url = NSURL(fileURLWithPath: path!)
        webView.loadHTMLString(try! String(contentsOfURL: url), baseURL: url.URLByDeletingLastPathComponent)

        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

I think the key point here is baseUrl parameter, you should setup it correctly. In my case i've used html's url without last path component - e.g. containing folder. This works fine on both device & simulator - check device snapshot. I've uploaded sample project to https://github.com/soxjke/WKWebViewTest so you can take a look (i've removed codesigning info from git)

Device snapshot

So, to recap - method is working, functionality is working, just you do something wrong with it. To help you get what's wrong with your solutions, i'll add some suggestions: 1. Remember, that simulator filesystem is case-insensitive, device filesystem is case-sensitive. So if you have your filenames in html in lowercase - this won't work on device. 8fFsD.png != 8ffsd.png 2. Remember, that when copying resources, XCode ignores your folder structure. So if your html has <img src="./img/1.png"> and your XCOde project has folder structure like

test.htm

img/

    1.png 
    2.png

After build it will be flattened, so test.htm and 1.png and 2.png will reside on same level

test.htm
1.png 
2.png

I'm almost sure, after you verify these two assumptions, you'll get this method working.

Petro Korienev
  • 4,007
  • 6
  • 34
  • 43
  • 1
    Thanks for sharing the sample project on GitHub. With that I've been able to make the webview load the resources on my iPhone. I haven't quite figured out what's wrong with my implementation and why it's working only in the Simulator but I'll look into it as soon as I get the time and will post my findings in another answer on this page. – Mischa Oct 17 '16 at 10:59
  • Glad that my answer helps. As i've written, i suggest to check filenames and case first, and folder structure next. 99% you will find an issue. – Petro Korienev Oct 17 '16 at 13:10
  • 1
    Solved my problem. Thanks! – TotoroTotoro Sep 09 '17 at 04:27
  • Could you please check my question where HTMLString is a requirement for me, facing same issue https://stackoverflow.com/questions/66050435/images-from-document-directory-is-not-visible-in-webview-it-is-working-fine-for – PJR Feb 05 '21 at 07:55
13

I had this problem today, I've found the solution and potentially the cause:

loadHTMLString(String, baseURL: URL?) 

This function doesn't allow the rendered HTML to access local media, as far as I'm aware, this is because it would be an injection risk, this could allow rendered HTML to access and manipulate your local file system. With a html string, that could come from anywhere or anyone.

loadFileURL(URL, allowingReadAccessTo: URL)

With this function, you point the WKWebview to the html file in your FileManager, and to the containing folder with 'allowingReadAccessTo'. Because the html is stored within the FileManager, it will allow the rendered HTML to access locally stored media.

If you don't have the html file stored locally for some reason(I assume you do), You could write the html sting into a .html file, then point to the URL of that file. However, this is just subverting Apple's protection, so do it at your own peril (don't do it).

This is just the solution that worked for me and my understanding of why we're having the problem to begin with.

Edit #1: Typo.

Edit #2: I've since found another nuance, When stating the 'allowingReadAccessTo:' URL, if the HTML itself needs to access things in parent folders (ie: .css, .js files), you need to specify the parent folder, not necessarily the location of the HTML itself, this will then implicitly allow access to the child folders as required also. For me, this problem was only apparent on a physical device, this didn't seem to have an effect whilst running in simulator, likely another discrepancy between how permissions work on simulator and a physical device.

Scott Browne
  • 175
  • 1
  • 8
  • 2
    This was it! CSS stopped working after moving to WKWebView which worked perfectly fine in UIWebView. Now that I save html to a file and read it as file url with `allowingReadAccessTo:` to, everything loads fine. – green0range Feb 10 '20 at 19:50
  • @green0range if u could help me out https://stackoverflow.com/questions/63800637/unable-to-load-html-with-only-images-in-wkwebview-ios-13-swift-5 – iMinion Sep 08 '20 at 20:41
3

Well you should be able to use local images and CSS files (and JavaScript files for that matter) with WKWebViews with the function that you have already found. My guess is that the problem is with your baseURL variable.

Update 7.5.2017:

I have completely updated the code from another SO answer of mine that used to be linked to my answer here. I have a working project for loadHTMLString() and .loadFileURL()

Community
  • 1
  • 1
tech4242
  • 2,348
  • 2
  • 23
  • 33
  • 1
    Have you tried that on a real device? Because this is pretty much what I have done. The `baseURL` is supposed to be the location from which your relative paths are resolved but the issue can't really be related to that because I've tried referencing absolute paths in the HTML as well and it doesn't work either. – Mischa Oct 15 '16 at 16:40
  • (Btw: Once you figure out the quirks *everything* is quite alright. There shouldn't be any quirks in the first place!) – Mischa Oct 15 '16 at 16:42
3

Personally, I had to switch to using XWebView as the out-of-the-box behavior of WKWebView does not allow loading of local files. XWebView tricks it by loading up a local web server in the background and directing local traffic thru it. (XWebView is based on top of WKWebView)

Seems a bit overkill, but that is what I ended up having to do.

Chris Brandsma
  • 11,666
  • 5
  • 47
  • 58
  • Thanks for the adivce. I've read about this web server trick in other posts. But it really feels like a big overkill to use an external library to setup a local web server just to render some basic HTML - a task that's a webview's main purpose! – Mischa Oct 15 '16 at 16:48
3

I've been experimenting with this as well, with similar restrictions, and the problem appears to be that paths aren't resolved unless baseURL references the application bundle. It doesn't work if you, for example, have something in the application's documents.

Edit: I have filed a radar for this rdar://29130863

ianyh
  • 619
  • 5
  • 8
  • I have the exact same problem as the rdar. Any chance you remember what you end up doing? :) – ozd Feb 02 '20 at 13:06
3

Try to create baseURL using:

let baseURL = URL(fileURLWithPath: "#path#")

instead of:

let baseURL = URL(string: "#path#")

The main difference is that the first method adds file:// prefix before the path.

kowal
  • 899
  • 8
  • 9
2

You can base64 encode the images... I know that works. Not sure if it will be appropriate for your use case though.

Kind of funny, I just ran into this problem while doing the opposite - moving from base64 encoded to image files.

moliveira
  • 798
  • 8
  • 17
  • Sounds like a smart work-around! Will try that. However, it really feels like dirty hacking in my case because I'll have to parse the HTML, find all image URLs, encode them as base64 data and replace the URL with the data. – Mischa Oct 06 '16 at 19:20
  • yea, I can see that. In my case I already have the image that i want to get into the html so it was a bit cleaner. – moliveira Oct 06 '16 at 19:31
  • I've tried base64 encoding the images and it still does not work. https://github.com/starkindustries/Print2PDFTest – Zion Nov 29 '18 at 14:01
2

When I used UIWebview, I used baseURL as,

let baseUrl = NSURL(string: Bundle.main.path(forResource: "cms", ofType: "html")!)! as URL

webView.loadHTMLString(bodyPage, baseURL: baseUrl)

But for the WKWebView, I used baseURL as

let baseUrl = Bundle.main.bundleURL

webView.loadHTMLString(bodyPage, baseURL: baseUrl)

This works for me.

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
lbear9090
  • 61
  • 3
1

I know this is quite old already, but I ran into the exact same problem and it took me hours of trials and even to find this thread with the same problem (Xamarin Forms App)

My issue was: parsing remote HTML content into a string and also adding locally saved images (also downloaded dynamically, no resource of the app). On the simulator all works well, but on acutal device the local images are not showing (also no ? or anything indicating an error, just a blank frame). The Xamarin webview also offers the "BaseURL" option which didn't help, also not to use the BaseURL on the custom iOS wkWebView.

The only working solution as pointed out by Scott above, is to write the HTML into a file and then use the "LoadFileUrl" function and allow read access to the base directory. This also works with absolute file paths for images in the HTML (not only relative to the basedir, but of course somewhere within the basedir).

My custom webview renderer to load web and local content looks like this now:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
    base.OnElementPropertyChanged(sender, e);
    NSUrl baseURL = new NSUrl(App.dirNews, true);
    string viewFile = Path.Combine(App.dirNews, "view.html");
    NSUrl fileURL = new NSUrl(viewFile, false);

    switch (e.PropertyName) {
        case "Url":
            System.Console.WriteLine("--- Loading Web page ---");
            System.Console.WriteLine("--- " + Element.Url + " ---");
            NSUrlRequest myRequest = new NSUrlRequest(new NSUrl(Element.Url), NSUrlRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, 120);
            Control.LoadRequest(myRequest);
            break;

        case "HTML":
            System.Console.WriteLine("--- Showing HTTP content ---");
            File.WriteAllText(viewFile, Element.HTML, System.Text.Encoding.UTF8);
            Control.LoadFileUrl(fileURL, baseURL);
            break;
    }
}
Mischa
  • 15,816
  • 8
  • 59
  • 117
Kerry
  • 101
  • 1
  • 7
1

It took me a while to figure this out, but based on this answer I got it working:

https://stackoverflow.com/a/73519282/5868066

Try this:

let htmlPath = URL(fileURLWithPath: "")
let htmlDirectory = htmlPath.deletingLastPathComponent() 
let htmlString = try! String(contentsOfFile: htmlPath.path, encoding: .utf8) 

let baseURL = URL(fileURLWithPath: htmlDirectory)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[.zero]

webView.loadFileURL(htmlPath, allowingReadAccessTo: documentsDirectory)
webView.loadHTMLString(htmlString, baseURL: baseURL)

klaudas
  • 427
  • 5
  • 13
0

I was able to reproduce a similar issue. WKWebView loads my images specially if they are located remotely, apart from my app server.

For servers that are not SSL-secured (http instead of https), you can set your info.plist as per below:

App Transport Security Settings

 - Allow Arbitrary Loads in Web Content (Set to YES)
 - Allow Arbitrary Loads (Set to YES)

The problem was actually in the server. The server application was either:

  • Changing the image src from "http://IP-or-domain/uploads/file.jpg" to "../../uploads/file.jpg"

- OR -

  • The image src was "http://localhost/uploads/file.jpg" or "http://127.0.0.1/uploads/file.jpg" instead of "http://YOUR-SERVER-IP-ADDRESS/uploads/file.jpg"

In these cases, the actual device wont be able to locate the image. This only works with iOS Simulator because the virtual device is the same as the server and development machine. It can read LOCALHOST and 127.0.0.1.

In my server, I was using a Rich Text Editor (TinyMCE) and it automatically removes the IP address after it detects that it's the same source.

0

WKWebView can load image or css file from NSTemporaryDirectory, so you can copy your files to NSTemporaryDirectory, and then load it. It works for me on iOS 14! see this issue. ios-wkwebview-loadhtmlstring-baseurl-fails-to-load-images-and-read-css

Phil
  • 201
  • 1
  • 7