WKWebView无法在iOS 8下加载本地文件


123

对于以前的iOS 8的测试版,加载本地Web应用程序(捆绑)和它的作品罚款两UIWebViewWKWebView,我甚至使用新问世的网页游戏WKWebView API。

var url = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("car", ofType:"html"))

webView = WKWebView(frame:view.frame)
webView!.loadRequest(NSURLRequest(URL:url))

view.addSubview(webView)

但是在Beta 4中,我只有空白的空白屏幕(UIWebView仍在工作),看起来没有任何内容被加载或执行。我在日志中看到错误:

无法为其创建沙箱扩展 /

有任何指导我正确方向的帮助吗?谢谢!


另外,尝试将webView添加到viewDidLoad中的视图层次结构中,然后在viewWillAppear中加载请求。我的WKWebView仍在工作,但这就是我的工作方式。也许WebView进行了优化,以使请求不在视图层次结构中时不加载请求?
rvijay007

完成(viewDidLoad中的view.addView和viewWillAppear中的loadRequest),我得到了相同的白屏和相同的错误消息。
Lim Thye Chean 2014年

9
这似乎仅在设备上发生,因为在模拟器上工作正常。我正在使用Objc。
GuidoMB 2014年

1
在XCode 6-beta 7中,这仍然是一个问题。我的临时解决方案是使用github.com/swisspol/GCDWebServer提供本地文件。
GuidoMB 2014年

2
有人在iOS 8.0.1下测试过吗?
Lim Thye Chean 2014年

Answers:


106

他们终于解决了错误!现在我们可以使用了-[WKWebView loadFileURL:allowingReadAccessToURL:]。显然,该修复程序在WWDC 2015视频504 Safari View Controller简介中值得花费几秒钟

https://developer.apple.com/videos/wwdc/2015/?id=504

适用于iOS8〜iOS10(Swift 3)

正如Dan Fabulish的回答所说,这是WKWebView的一个错误,显然它不会很快解决。,正如他所说,这是一种解决方法:)

我之所以回答,只是因为我想在这里显示解决方法。https://github.com/shazron/WKWebViewFIleUrlTest中显示的IMO代码充满了无关的细节,大多数人可能对此并不感兴趣。

解决方法是20行代码,包括错误处理和注释,无需服务器:)

func fileURLForBuggyWKWebView8(fileURL: URL) throws -> URL {
    // Some safety checks
    if !fileURL.isFileURL {
        throw NSError(
            domain: "BuggyWKWebViewDomain",
            code: 1001,
            userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("URL must be a file URL.", comment:"")])
    }
    try! fileURL.checkResourceIsReachable()

    // Create "/temp/www" directory
    let fm = FileManager.default
    let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("www")
    try! fm.createDirectory(at: tmpDirURL, withIntermediateDirectories: true, attributes: nil)

    // Now copy given file to the temp directory
    let dstURL = tmpDirURL.appendingPathComponent(fileURL.lastPathComponent)
    let _ = try? fm.removeItem(at: dstURL)
    try! fm.copyItem(at: fileURL, to: dstURL)

    // Files in "/temp/www" load flawlesly :)
    return dstURL
}

可以用作:

override func viewDidLoad() {
    super.viewDidLoad()
    var fileURL = URL(fileURLWithPath: Bundle.main.path(forResource:"file", ofType: "pdf")!)

    if #available(iOS 9.0, *) {
        // iOS9 and above. One year later things are OK.
        webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
    } else {
        // iOS8. Things can (sometimes) be workaround-ed
        //   Brave people can do just this
        //   fileURL = try! pathForBuggyWKWebView8(fileURL: fileURL)
        //   webView.load(URLRequest(url: fileURL))
        do {
            fileURL = try fileURLForBuggyWKWebView8(fileURL: fileURL)
            webView.load(URLRequest(url: fileURL))
        } catch let error as NSError {
            print("Error: " + error.debugDescription)
        }
    }
}

2
对其进行了一些修改,以复制整个文件夹,图像以及全部。 let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf

1
Piwaf的解决方案对我来说效果更好,请注意,尽管上面的示例是“ self.webView”而不是“ self.loadingWebView”
Patrick

2
谢谢@ nacho4d。tldr; / tmp文件夹解决方案不适用于8.0,但适用于8.0.2。我在使该解决方案在我的测试设备上运行时遇到了麻烦,最终克隆了shazron的存储库以进行尝试。那也不起作用。事实证明,shazron的解决方案无法在我的设备在iPhone 6 Plus上运行8.0(12A366)的iOS版本上运行。我在运行iOS 8.0.2的设备(iPad Mini)上进行了尝试,并且工作正常。
jvoll

1
应该让_ =试试吗?fm.removeItemAtURL(dstURL)而不是让_ =试试?fileMgr.removeItemAtURL(dstURL)
2013年

3
仅适用于简单的网站。如果您使用的是ajax或通过angular加载本地视图,请期望“仅HTTP支持跨源请求”。您唯一的后备方法是本地Web服务器方法,我不喜欢它,因为它在本地网络上可见。这需要在岗位上注意,节省一些时间。
jenson-button-event

83

WKWebView无法通过其loadRequest:方法从文件URL加载内容。http://www.openradar.me/18039024

您可以通过加载内容loadHTMLString:,但是如果您的baseURL是一个文件:URL,那么它将仍然无法使用。

iOS 9有一个新的API,可以满足您的需求 [WKWebView loadFileURL:allowingReadAccessToURL:]

有一种针对iOS 8的解决方法,由shazron在Objective-C的https://github.com/shazron/WKWebViewFIleUrlTest上演示,可将文件复制到其中/tmp/www并从中加载

如果您在Swift中工作,则可以尝试nachos4d的示例。(它也比shazron的示例短得多,因此,如果您在使用shazron的代码时遇到麻烦,请尝试一下。)


1
一种解决方法(在这里提到:devforums.apple.com/message/1051027)是将内容移动到tmp并从那里访问它。我的快速测试似乎表明它确实有效...
Mike M

6
在8.1中未修复。
马特2014年

1
将文件移动到/ tmp对我有用。但是...天哪,这是如何通过测试的?
格雷格·马莱蒂奇

1
示例只是一个演示的方式。要读取的文件必须在内部/tmp/www/。使用NSTemporaryDirectory()NSFileManager创建www目录(因为默认情况下没有这样的目录)。然后将文件复制到此处,然后阅读此文件:)
nacho4d

2
8.3仍然不固定...?
ninjaneer 2015年

8

iOS 9上如何使用[WKWebView loadFileURL:allowingReadAccessToURL:]的示例。

将Web文件夹移至项目时,选择“创建文件夹引用”

在此处输入图片说明

然后使用类似下面的代码(Swift 2):

if let filePath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp/index.html"){
  let url = NSURL(fileURLWithPath: filePath)
  if let webAppPath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp") {
    let webAppUrl = NSURL(fileURLWithPath: webAppPath, isDirectory: true)
    webView.loadFileURL(url, allowingReadAccessToURL: webAppUrl)
  }
}

在html文件中使用这样的文件路径

<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">

不像这样

<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">

已移至xcode项目的目录的示例。

在此处输入图片说明


6

临时解决方法:我使用的是GuidoMB建议的GCDWebServer。

我首先找到捆绑的“ www /”文件夹(其中包含“ index.html”)的路径:

NSString *docRoot = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"www"].stringByDeletingLastPathComponent;

...然后像这样启动它:

_webServer = [[GCDWebServer alloc] init];
[_webServer addGETHandlerForBasePath:@"/" directoryPath:docRoot indexFilename:@"index.html" cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:port bonjourName:nil];

要停止它:

[_webServer stop];
_webServer = nil;

即使在iPad 2上,性能看起来也不错。


我没有通知应用程序后崩溃进入后台,所以我停止它applicationDidEnterBackground:applicationWillTerminate:; 我在application:didFinishLaunching...和启动/重新启动它applicationWillEnterForeground:


1
使用“服务器”可能会吞噬WKWebView提供超过的任何性能优势UIWebView。在解决此问题之前,最好还是坚持使用旧的API。
射线

@ray不适用于单页应用程序。
EthanB 2014年

我已经在我的应用程序的内部版本中使GCDWebServer正常工作。而且,如果您的应用程序中有很多javascript,那么服务器绝对值得。但是还有其他一些 问题使我目前无法使用WKWebView,因此我希望在iOS 9中有所改进。–
Tom Hamming

以及如何在WebView上显示它?我真的不明白GCDWebServer是做什么用的?
chipbk10 2015年

1
而不是将Webview指向“ file:// .....”,而是将其指向“ http:// localhost:<port> / ...”。
EthanB,2015年

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

这为我解决了iOS 8.0+ dev.apple.com的问题

这似乎也很好...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];

除了FILE之外,您也可以放置DIR。
nullqube

这是一个很棒的发现。我不知道为什么它没有被投票赞成。
plivesey

这篇文章有更多信息(包括指向Webkit的链接):stackoverflow.com/questions/36013645/…–
plivesey

configuration.preferences setValue将在iOS 9.3上崩溃
Vignesh Kumar,

我正在使用iOS 13 SDK,但尝试保持与iOS 8的兼容性,并且该allowFileAccessFromFileURLs方法因崩溃NSUnknownKeyException
arlomedia

4

除了Dan Fabulich提到的解决方案之外,XWebView是另一个解决方法。[WKWebView loadFileURL:allowingReadAccessToURL:]通过扩展实现


1
在查看此问题的其他解决方法后,我决定使用XWebView。XWebView是在Swift中实现的框架,但是我在iOS 8 Objective-C应用程序中使用它时没有问题。
chmaynard,2015年

4

我还不能发表评论,所以我将其发布为单独的答案。

这是nacho4d解决方案的Objective-C版本。到目前为止,我所看到的最好的解决方法。

- (NSString *)pathForWKWebViewSandboxBugWithOriginalPath:(NSString *)filePath
{
    NSFileManager *manager = [NSFileManager defaultManager];
    NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"www"];
    NSError *error = nil;

    if (![manager createDirectoryAtPath:tempPath withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"Could not create www directory. Error: %@", error);

        return nil;
    }

    NSString *destPath = [tempPath stringByAppendingPathComponent:filePath.lastPathComponent];

    if (![manager fileExistsAtPath:destPath]) {
        if (![manager copyItemAtPath:filePath toPath:destPath error:&error]) {
            NSLog(@"Couldn't copy file to /tmp/www. Error: %@", error);

            return nil;
        }
    }

    return destPath;
}

1
我无法在模拟器中的iOS 8上使用它。我想将.png放在/ tmp / www中,然后在html中使用img标签。img标签src应该使用什么?
user3246173

正是我所需要的。谢谢!
叮当2015年

3

如果您尝试在较大的HTML字符串中显示本地图像,例如:<img src="file://...">,则该图像仍不会显示在设备上,因此我将图像文件加载到NSData中并能够通过将src字符串替换为来显示它数据本身。帮助构建HTML字符串以加载到WKWebView的示例代码,其中的结果将替换src =“”引号内的内容:

迅速:

let pathURL = NSURL.fileURLWithPath(attachmentFilePath)
guard let path = pathURL.path else {
    return // throw error
}
guard let data = NSFileManager.defaultManager().contentsAtPath(path) else {
    return // throw error
}

let image = UIImage.init(data: data)
let base64String = data.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
result += "data:image/" + attachmentType + "base64," + base64String

var widthHeightString = "\""
if let image = image {
    widthHeightString += " width=\"\(image.size.width)\" height=\"\(image.size.height)\""
}

result += widthHeightString

目标C:

NSURL *pathURL = [NSURL fileURLWithPath:attachmentFilePath];
NSString *path = [pathURL path];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];

UIImage *image = [UIImage imageWithData:data];
NSString *base64String = [data base64EncodedStringWithOptions:0];
[result appendString:@"data:image/"];
[result appendString:attachmentType]; // jpg, gif etc.
[result appendString:@";base64,"];
[result appendString:base64String];

NSString *widthHeightString = @"\"";
if (image) {
    widthHeightString = [NSString stringWithFormat:@"\" width=\"%f\" height=\"%f\"", image.size.width, image.size.height];
}
[result appendString:widthHeightString];

在快速版本中,请在base64之前添加分号。 result += "data:image/" + attachmentType + ";base64," + base64String
shahil

谢谢,这是唯一对我有用的方法,因为我将.gif文件下载到设备上的temp文件夹中,然后将该文件WKWebView作为<img>HTMLstring中的加载到中。
Zystem先生

1

我正在使用以下内容。还有一些我正在处理的东西,但是您可以看到我在哪里注释了loadRequest并正在替换loadHTMLString调用。希望这对他们有所帮助,直到他们修复了该错误。

import UIKit
import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {

    var theWebView: WKWebView?

    override func viewDidLoad() {
        super.viewDidLoad()

        var path = NSBundle.mainBundle().pathForResource("index", ofType: "html", inDirectory:"www" )
        var url = NSURL(fileURLWithPath:path)
        var request = NSURLRequest(URL:url)
        var theConfiguration = WKWebViewConfiguration()

        theConfiguration.userContentController.addScriptMessageHandler(self, name: "interOp")

        theWebView = WKWebView(frame:self.view.frame, configuration: theConfiguration)

        let text2 = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)

        theWebView!.loadHTMLString(text2, baseURL: nil)

        //theWebView!.loadRequest(request)

        self.view.addSubview(theWebView)


    }

    func appWillEnterForeground() {

    }

    func appDidEnterBackground() {

    }

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

    func userContentController(userContentController: WKUserContentController!, didReceiveScriptMessage message: WKScriptMessage!){
        println("got message: \(message.body)")

    }

}

3
这似乎仅对HTML文件有效,而对其他资源无效。
Lim Thye Chean 2014年

1

对于谁必须在iOS8下解决此问题:

如果您的页面并不复杂,则可以选择将该页面作为单页面应用程序。

换句话说,将所有资源嵌入到html文件中。

为此:1.将js / css文件的内容分别复制到html文件中的/标签中;2.将您的图像文件转换为svg,以替换相应的文件。3.例如,使用[webView loadHTMLString:baseURL:]像以前一样加载页面

它与设置svg图像的样式有些不同,但不会对您造成太大影响。

页面渲染性能似乎有所下降,但是值得在iOS8 / 9/10下使用这种简单的解决方法。



0

我设法在OS X上使用PHP的Web服务器。复制到临时目录/ www对我来说不起作用。Python SimpleHTTPServer抱怨要读取MIME类型,这可能是沙箱问题。

这是使用的服务器php -S

let portNumber = 8080

let task = NSTask()
task.launchPath = "/usr/bin/php"
task.arguments = ["-S", "localhost:\(portNumber)", "-t", directoryURL.path!]
// Hide the output from the PHP server
task.standardOutput = NSPipe()
task.standardError = NSPipe()

task.launch()

0

@ nacho4d解决方案很好。我想稍作更改,但是我不知道如何在您的帖子中进行更改。所以我把它放在这里,希望您不要介意。谢谢。

如果您有www文件夹,则还有许多其他文件,例如png,css,js等。然后,您必须将所有文件复制到tmp / www文件夹。例如,您有一个www文件夹,如下所示: 在此处输入图片说明

然后在Swift 2.0中:

override func viewDidLoad() {
    super.viewDidLoad()

    let path = NSBundle.mainBundle().resourcePath! + "/www";
    var fileURL = NSURL(fileURLWithPath: path)
    if #available(iOS 9.0, *) {
        let path = NSBundle.mainBundle().pathForResource("index", ofType: "html", inDirectory: "www")
        let url = NSURL(fileURLWithPath: path!)
        self.webView!.loadRequest(NSURLRequest(URL: url))
    } else {
        do {
            fileURL = try fileURLForBuggyWKWebView8(fileURL)
            let url = NSURL(fileURLWithPath: fileURL.path! + "/index.html")
            self.webView!.loadRequest( NSURLRequest(URL: url))
        } catch let error as NSError {
            print("Error: \(error.debugDescription)")
        }
    }
}

函数fileURLForBuggyWKWebView8是从@ nacho4d复制的:

func fileURLForBuggyWKWebView8(fileURL: NSURL) throws -> NSURL {
    // Some safety checks
    var error:NSError? = nil;
    if (!fileURL.fileURL || !fileURL.checkResourceIsReachableAndReturnError(&error)) {
        throw error ?? NSError(
            domain: "BuggyWKWebViewDomain",
            code: 1001,
            userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("URL must be a file URL.", comment:"")])
    }

    // Create "/temp/www" directory
    let fm = NSFileManager.defaultManager()
    let tmpDirURL = NSURL.fileURLWithPath(NSTemporaryDirectory())
    try! fm.createDirectoryAtURL(tmpDirURL, withIntermediateDirectories: true, attributes: nil)

    // Now copy given file to the temp directory
    let dstURL = tmpDirURL.URLByAppendingPathComponent(fileURL.lastPathComponent!)
    let _ = try? fm.removeItemAtURL(dstURL)
    try! fm.copyItemAtURL(fileURL, toURL: dstURL)

    // Files in "/temp/www" load flawlesly :)
    return dstURL
}

-1

尝试使用

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

似乎仍在工作。然而。


谢谢,我会尝试的。
Lim Thye Chean 2014年

3
这仅适用于HTML文件本身而不适用于资源,对吗?
Lim Thye Chean 2014年

1
不幸的是,是的,似乎只有HTML文件正在加载。我们希望这只是一个错误,而不是加载本地文件的新限制。我正在尝试在WebKit源文件中找到此错误。
Oleksii 2014年

1
我遇到了同样的问题-加载了HTML文件,但是未加载本地文件系统上的图像和其他资源。在控制台中,WebKit抛出错误“不允许加载本地资源”。我为此提交了一个错误,雷达编号17835098
mszaro
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.