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.)
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:
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.
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:
☑️ 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. :)
This solution helped me:
[configuration.preferences setValue:#YES forKey:#"allowFileAccessFromFileURLs"];
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.")
}
}
}
}
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)
}
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)
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)
}
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)
}
Related
I am making PDF using TPPDF library. This makes the PDF fine and returns a URL which is like this:
pdfURL = file:///Users/taimoorarif/Library/Developer/CoreSimulator/Devices/8A2723A7-DD69-4551-A297-D30033734181/data/Containers/Data/Application/EE60FB55-13AE-4658-A829-8A85B2B6ED95/tmp/SwiftUI.pdf
If I open this URL in Google Chrome, this shows my PDF. But when I try to use
UIApplication.shared.open(pdfURL)
It did nothing.
I also made a UIViewRepresentable PDFKitView:
import SwiftUI
import PDFKit
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
And use it as:
PDFKitView(url: URL(string: pdfURL.absoluteString))
It also not worked.
I know this url is the path where this file is saved. So, after searching on this, I tried:
let fileURL = Bundle.main.url(forResource: pdfURL, withExtension: "pdf")!
And application is crashing here with error:
Unexpectedly found nil while unwrapping an Optional value
The question here is I want to open this PDF in my APP, whether it opens on Safari or Google Drive but don't know how to do that. So how can I open my PDF?
Bundle.main is not FileManager. Bundle.main is for accessing your app's resources like a video, image that you added to your project using Xcode.
URL(string:) is meant for online urls. To initialize an URL for a file use URL(fileURLWithPath: anyURL.path). So what you really should do is:
PDFKitView(url: URL(fileURLWithPath: pdfURL.path))
or
PDFKitView(url: pdfURL)
I am trying to load a local html file which is downloaded to my application with wkwebview. Along with this html file i am downloading two js files, they are referenced inside the html file.
But what ever i do, they are not loading in wkwebview
#IBOutlet var webView: WKWebView!
#IBOutlet var act: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Adding webView content
webView.uiDelegate = self
webView.navigationDelegate = self
let fm = FileManager.default
let docsurl = try! fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let myurl = docsurl.appendingPathComponent("index.html")
print("my url = ",myurl )
// myurl prints file///var/some directories/index.html
let request = URLRequest(url: myurl!)
webView.load(request)
}
My html file
<!DOCTYPE html>
<html>
<head>
<script src="jquery.min.js"></script>
<script>
$(document).ready(function(){
$("button").click(function(){
$("p").hide();
});
});
</script>
</head>
<body>
<h2>This is a heading</h2>
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
<button>Click me</button>
</body>
</html>
But it is not loading on the screen from safari(Develop/) i can see that An error occured trying to load the resource, page is coming as about:blank
swift 4.2
//load HTML
let bundle = Bundle.main
var path = bundle.path(forResource: "index", ofType: "html")
var html = ""
do {
try html = String(contentsOfFile: path!, encoding: .utf8)
} catch {
//ERROR
}
let urlstr = "http://website.com"
webViewWK.loadHTMLString(html, baseURL: URL(string: urlstr)!)
URLRequest is basically made for live url. For system file just load the URL. You need to use below method and it will work:
/*! #abstract Navigates to the requested file URL on the filesystem.
#param URL The file URL to which to navigate.
#param readAccessURL The URL to allow read access to.
#discussion If readAccessURL references a single file, only that file may be loaded by WebKit.
If readAccessURL references a directory, files inside that file may be loaded by WebKit.
#result A new navigation for the given file URL.
*/
#available(iOS 9.0, *)
open func loadFileURL(_ URL: URL, allowingReadAccessTo readAccessURL: URL) -> WKNavigation?
So that:
self.webView.loadFileURL(myurl, allowingReadAccessTo: myurl)
I am trying to add some local css to my webview but it seems that I have a problem with the css file in my app :
Nothing happens when I try to load the css from the local css file but when I replace '%#' by a online css file it works fine.
Thank you for your help.
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.delegate = self
let html: String! = "<html><head></head><body><div id=\"postcontent\"><h1>Hello</h1></div></body></html>"
webview.loadHTMLString(html, baseURL: nil)
}
func webViewDidFinishLoad(_ webView: UIWebView){
let path = Bundle.main.path(forResource: "styles", ofType: "css")
let javaScriptStr: NSString = "var link = document.createElement('link'); link.href = '%#'; link.rel = 'stylesheet'; document.head.appendChild(link)"
let javaScripthPath = NSString(format: javaScriptStr, path!)
webview.stringByEvaluatingJavaScript(from: javaScripthPath as String)
print(javaScripthPath)
}
}
Your HTML file is loaded from a string, so it's loaded into a web view that can't access files on the file system (probably due to the Same Origin Policy that browsers implement). It can only access resources similarly injected in via loadHTMLString.
If you want to use local CSS, load your HTML from a file instead of a string. This will give the web view access to the CSS if it's in the same directory.
Here is a demo, first, the HTML is now a file.
Then, your code can look like this:
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.delegate = self
let path = Bundle.main.path(forResource: "app", ofType: "html")!
let url = URL(fileURLWithPath: path)
let request = URLRequest(url: url)
webview.loadRequest(request)
}
func webViewDidFinishLoad(_ webView: UIWebView){
let path = Bundle.main.path(forResource: "styles", ofType: "css")!
let javaScriptStr = "var link = document.createElement('link'); link.href = '\(path)'; link.rel = 'stylesheet'; document.head.appendChild(link)"
webview.stringByEvaluatingJavaScript(from: javaScriptStr)
}
}
I need to load local HTML, CSS and JS into an iOS app to make my boss happy. I'm using Xcode 8.3.1 and Swift 3. I have created a new project with a WebView placed in my Main.storyboard
Other StackOverflow resources helped me get this far:
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let urlpath = Bundle.main.path(forResource: "index", ofType: "html");
let requesturl = URL(string: urlpath!)
let request = URLRequest(url: requesturl!)
webView.mainFrame.load(request)
}
I get this error:
Value of type 'UIWebView' has no member 'mainFrame'
When I remove mainFrame it says I'm missing a mimeType. Can anyone help? I'm obviously a noob to Swift.
mainFrame is from the class WebView which is only for macOS.
Since you are using UIWebView, you need to use webView.loadRequest.
You also have a problem with your URL. Either use URL(fileURLWithPath:) to create the URL from the path string or, better yet, use Bundle.main.url(forResource:withExtension:) and get the URL directly.
You might also want to use WKWebView instead of UIWebView. It has a lot more features and it may be more useful for your needs.
Swift 5
let path = Bundle.main.path(forResource: "temp", ofType: "html")!
//reading
var text = try! String.init(contentsOfFile: path, encoding: String.Encoding.utf8)
text = text.replacingOccurrences(of: "${contents}", with: contents)
// load string
let bundleURL = URL.init(string: path)
webView.loadHTMLString(text, baseURL: bundleURL)
done!
Is it possible to load script files from Xcode project via html loaded with WKWebView loadHTMLString method?
For example if I have a project named DemoProject, with javascript files called script1.js and script2.js in the root directory. Then I'm loading an html string that tries to reference those files. Is that possible? If so how do I properly reference those files?
Try injecting it as user script.
If your folder structure is like this, use the below function to load it as user script
private func fetchScript() -> WKUserScript!{
var jsScript = ""
if let jsPath = Bundle.main.path(forResource: "hello", ofType: "js", inDirectory: "scripts"){
do
{
jsScript = try String(contentsOfFile: jsPath, encoding: String.Encoding.utf8)
}catch{
print("Error")
}
}
let wkAlertScript = WKUserScript(source: jsScript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
return wkAlertScript
}
Add it to controller
func registerScriptsAndEvents() {
let controller = self.wkWebView.configuration.userContentController
// Load the entire script to the document
controller.addUserScript(fetchScript())
}