WKWebView 确实从本地文档文件夹加载资源

WKWebView does load resources from local document folder

在我的 Swift iOS 应用程序中,我想从远程服务器下载一些动态 HTML 页面,将它们保存在文档目录中,并从文档目录中显示这些页面.

我用它来加载页面:

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

在模拟器上一切正常,但当我转到真机时,它只显示一个空白页面。然后我使用 Safari 连接到应用程序,发现它抱怨 "Failed to load resource"。

然后我尝试先阅读 htmlPath 页面的内容,然后使用

appWebView!.loadHTMLString()

加载页面。它在 HTML 页面很简单时有效。但是,如果 HTML 引用了其他内容,即也在文档目录中的 JavaScript 文件(具有类似 <script src="file:////var/mobile/Containers/Data/Application/762035C9-2BF2-4CDD-B5B1-574A0E2B0728/Documents/xxxxx.js"> 的绝对路径),它将无法加载。

有谁知道为什么会这样,以及如何解决这个问题?

更多信息:

这是我在我的一个项目(iOS 10,Swift 3)中用来加载本地文件的简化版本。根据 Raghuram 和 Fox5150 在 iOS 10.3.1 和 iPhone 7+ 上再次测试后,我刚刚 更新了我的代码 (7.5.2017)评论。

我刚刚创建了一个全新的项目,这是文件夹结构:

2018 年 4 月 19 日更新: 添加了一个新功能,可以下载包含 HTML、CSS、JS 文件的 .zip,在 /Documents 中解压/ (Alamofire + Zip) 然后将这些文件加载​​到 webView 中。您也可以在 GitHub sample project 中找到它。再次强调,请随意分叉和加注星标! :)

更新 08.02.2018: 终于添加了一个 GitHub sample project,其中还包括一个本地 JavaScript 文件。随意分叉和加星! :)

版本 1 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
    }
}

版本 2 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
    }
}

需要注意的陷阱:

  • 确保您的本地 html/js/css 文件位于项目 -> 目标 -> 构建阶段 -> 复制捆绑资源
  • 确保您的 html 文件不引用相对路径,例如css/styles.css 因为 iOS 会压扁你的文件结构,而 styles.css 将与 index.html 处于同一水平,所以写 <link rel="stylesheet" type="text/css" href="styles.css"> 而不是

鉴于 2 个版本和问题,这里是我的 html/css 项目文件:

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;
}

如果有人想要 GitHub 示例项目,请在评论部分告诉我,我会上传它。

效果很好(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.")
            }
        }
    }
}

这个解决方案帮助了我:

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

Swift 4 方法

此方法允许 WKWebView 正确读取您的目录层次结构和 sub-directories 链接的 CSS/JS 文件。您 不需要 更改您的 HTML、CSS 或 JS 代码。

更新为 Xcode 9.3

第 1 步

将本地网络文件的文件夹导入到您的项目中。确保您:

☑️ Copy items if needed

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

☑️ Add to targets

第 2 步

使用 WKWebView 转到视图控制器并将以下代码添加到 viewDidLoad 方法:

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 – 要加载的文件的名称(不带 .html 扩展名)
  • website – 您的网络文件夹的名称(index.html 应该位于该目录的根目录)

结论

整体代码应如下所示:

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)
    }

}

如果大家对这个方法或代码还有其他疑问,我会尽力解答。 :)

检查那张票:

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)
}

这适用于文件 URL 或远程 URL,无论文件是在包中还是在文档中:

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

以这种方式构建 URL 允许我使用 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)

文件必须在文档目录中。

我执行了以下操作来检索文档:

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)
}