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.
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.
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.
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.
Two answers to another SO post suggested that usingloadFileURL(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.
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?!)
Load Local HTML File to a WKWebViewlet myUrl = myProjectBundle. url(forResource: "my-html-file", withExtension: "html")! where: my-html-file – is the name of HTML file you want to load into WKWebView from your App Bundle.
Here's how: Open the XIB or Storyboard you want to add the web view to in Interface Builder. Find the web view or WKWebView in the Object Library at the bottom-left of Interface Builder. Drag-and-drop a WKWebView object from the Object Library to your view controller's canvas, and adjust its size and position.
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.
You can implement WKWebView in Objective-C, here is simple example to initiate a WKWebView : WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; WKWebView *webView = [[WKWebView alloc] initWithFrame:self. view. frame configuration:theConfiguration]; webView.
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)
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.
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.
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