Loading local images into WKWebView
Asked Answered
M

6

16

I'm trying to get WKWebView to display locally downloaded images in a WKWebView. The webview normally displays HTML, which is retrieved remotely. The contents of the HTML can sometimes contain remote links to images. My app parses the HTML and looks for these HTML tags, downloads the file it is referencing and subsequently replaces the remote link with a local one.

Normally speaking, this wouldn't be very difficult but the images aren't being displayed, presumably due to the images and the local HTML files for the webview being in two separate directories (the documents directory and the app bundle directory respectively). I've seen people suggest moving the download destination of the images to the same directory as where the HTML files are but this isn't an option for me as I don't want to start mixing up files downloaded by the user with local assets.

What would be my best course of action here?

Mozzetta answered 18/9, 2017 at 14:30 Comment(5)
Are the images being displayed by themselves or in the context of a downloaded page? Both instances require different solutions.Tribade
@Tribade It's the latter.Mozzetta
Ok, I've run into this same issue before. Ideally, you want to make sure the directory that contains the HTML and the directory that contains the images both have a common ancestor directory. Secondly, make sure that your img ref urls in the HTML file reflect the native file paths that you have constructed after downloading. Lastly, when you load the html page, make sure you use loadFileURL(_:allowingReadAccessTo:) where the directory to allow access to is the common root directory the HTML and images share.Tribade
@Tribade Isn't this kind of the situation I'm trying to avoid, like how I described in the OP? Because this would involve either storing my app assets (my javascript files, which get fed the data I retrieve remotely) in the documents folder or saving the downloaded files to the app's bundle (is this even possible?). Either way, I'd be mixing app resources with user data which seems really messy.Mozzetta
Please see my answer for a more extensive solution.Numerical
M
10

Well, I've found a workaround. Instead of locally storing the images and referencing them in the HTML files, I'm now instead converting the images to Base64 and then adding them to the HTML. It's not ideal but it gets the job done. I'm going to leave this question open in case someone ever manages to find an actual solution.

Mozzetta answered 19/9, 2017 at 13:6 Comment(3)
After much searching on SO and elsewhere, this is what I have ended up doing as well. Works nicely but isn't ideal.Cassel
It seems there is a solution : #58385898Valenciavalenciennes
@Mozzetta - I tried converting image to Base64 and adding it. But it is not showing on WKWebview. Is there something I am missing? NSString *base64Str = [imageData base64EncodedStringWithOptions:0]; NSString *js =[NSString stringWithFormat:@"document.getElementById('image').src='data:image/png;base64,%@';", base64Str]; [self.wkWebView evaluateJavaScript:js completionHandler:nil];Blizzard
N
3

To display cached HTML referencing cached resources in a WKWebView:

  1. For each of the resources within your HTML content string, cache it into the directory as provided by NSTemporaryDirectory(). So an image tag like:

    ...<img src='https://www.myimage.com/example_image.png'/>...

    should be cached and replaced into something like this:

    ...<img src='/private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/example_image.png'/>...

  2. Now cache the HTML content string with the replaced resource URLs. It must also be cached in the directory provided by NSTemporaryDirectory(). One difference here is that it must be cached (and later referenced) using the file:// protocol as a restriction of caching the string using NSData (see sample code).

    For example file:///private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/my_html_content_string.html

A few things to point out:

  • You cannot load the HTML as a raw string (loadHTMLString:baseURL:).
  • You cannot reference the cached resource within your HTML string using the file:// protocol. That may work in a UIWebView, but will not work in the WKWebView.

Objective-C

// To cache the HTML string:
NSString *HTML = <HTML CONTENT WITH CACHED RESOURCES>;
NSData *data = [HTML dataUsingEncoding: NSUTF8StringEncoding];
[data writeToURL: cachedHTMLURL atomically: YES];

// To load the store HTML file:
[myWKWebView loadRequest: [NSURLRequest requestWithURL: cachedHTMLURL]]; // (file://.../tmp/my_html_content_string.html)

Swift

// To cache the HTML string:
let HTML = <HTML CONTENT WITH CACHED RESOURCES>
let data = HTML.data(using: String.Encoding.utf8)
do {
    try data.write(to: cachedHTMLURL, options: .atomic)
} catch {
    print(error)
}

// To load the store HTML file:
myWKWebView.load(URLRequest(url: cachedHTMLURL)) // (file://.../tmp/my_html_content_string.html)
Numerical answered 24/9, 2017 at 23:40 Comment(0)
M
2

I had the same problem with WKWebView as it can not load both html strings and images at the same time for security purposes. I switched to UIWebView, which is deprecated, but I was able to load both html strings and referenced images at the same time.

Milagro answered 16/7, 2019 at 3:31 Comment(1)
It is no longer a solution as Apple is about to stop accepting app submission (and app updates) which uses UIWebView https://mcmap.net/q/510563/-is-wkwebview-designed-as-a-replacement-of-uiwebviewTilton
C
2

I developed a definitive solution for the company I work for. But it relies on the html / javascript side. Anywhere inside your html code where you will reference to a local image <img src="..."/> you should set this "src" dynamically, and it will work seamlessly.

function getLocalURL(path) {
    let origin = window.location.origin
    if (origin == "file://") {
        return origin + window.location.pathname.replace("/index.html","") + path
    }
    return path
}

You should, clearly, rename index.html to whatever is your main .htm(l) filename :)

Usage:

getLocalURL("/local_images/location_icon.png")

Will return a WKWebView working path for the referenced local image path:

"file:///Users/arthurdapaz/Library/Developer/CoreSimulator/Devices/5073AF19-26A0-460E-BC82-E89100B8E1AB/data/Containers/Data/Application/2B099343-0BF5-4849-B1C2-2512377A9772/Documents/distDriver/local_images/location_icon.png"

Coachman answered 16/1, 2020 at 12:4 Comment(2)
It didn`t work for my MacOS WKWebview issue, any ideas?Filibertofilibuster
It didn't work for me neither, I tried on iphone simulator and real device...Tilton
C
1

The best approach to providing files is using the following setup:

  • create a WKWebViewConfiguration
  • call setURLSchemeHandler:forURLScheme: and provide a custom scheme handler for a custom scheme like foo://
  • instantiate a WKWebView using this configuration. You cannot assign a custom configuration to a nib-based WKWebView, so you may need to create a custom-view in the nib and add the WKWebView as subview.
  • load your html via URLs that have a custom scheme like foo:// instead of http://
  • as files are referred inside html mostly via relative URLs, like images/foo.jpg, the resulting URL will still be foo://yadayada/images/foo.jpg which means your handler is called for every single resource

That way it is easily possible to either provide in-memory files as resources into a WKWebView or you provide your own.

It may sound complicated but the custom scheme handler is actually easy to implement.

Cellist answered 8/2 at 19:38 Comment(0)
A
0

In case if you load your HTML locally from file, all you need to do is indicate the baseURL like this:

webView.loadHTMLString(htmlString, baseURL: Bundle.main.resourceURL) 

In HTML you simply indicate image filename without path:

<img src="homepage-logo.png" />

Note that the folder/group where you store this file doesn't matter. Xcode will flat your file structure when creating a bundle.

I know that this isn't exactly what was asked, but I consider it may be still useful for people like me who landed on this page.

Allocate answered 8/5 at 12:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.