【问题标题】:WKWebView not loading local files under iOS 8iOS 8 下 WKWebView 不加载本地文件
【发布时间】:2014-09-13 00:07:41
【问题描述】:

对于以前的 iOS 8 测试版,加载一个本地网络应用程序(在 Bundle 中),它适用于 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 不在视图层次结构中,也许 WebView 具有不加载请求的优化?
  • 完成(viewDidLoad 中的 view.addView 和 viewWillAppear 中的 loadRequest),我得到了相同的白屏和相同的错误消息。
  • 这似乎只发生在设备上,因为在模拟器上工作正常。顺便说一句,我正在使用 Objc。
  • 这仍然是 XCode 6 - beta 7 中的一个问题。我的临时解决方案是使用 github.com/swisspol/GCDWebServer 提供本地文件。
  • 有人在 iOS 8.0.1 下测试过吗?

标签: ios swift ios8 wkwebview


【解决方案1】:

他们终于解决了这个错误!现在我们可以使用-[WKWebView loadFileURL:allowingReadAccessToURL:]。 显然,修复在WWDC 2015 video 504 Introducing Safari View Controller 中值得几秒钟

适用于 iOS8 ~ iOS10 (Swift 3)

正如Dan Fabulish's answer 所说,这是 WKWebView 的一个错误 显然不会很快得到解决,正如他所说,有一个变通办法:)

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

解决方法是 20 行代码,包括错误处理和 cmets,不需要服务器 :)

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

【讨论】:

  • 对其进行一些修改以复制整个文件夹、图像和所有内容。 let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
  • 谢谢@nacho4d。 tldr; /tmp 文件夹解决方案不适用于 8.0,但适用于 8.0.2。我无法让这个解决方案在我的测试设备上运行,最终克隆了 shazron 的 repo 来试一试。那也没有用。事实证明,shazron 的解决方案不适用于我的设备在 iPhone 6 Plus 上运行 8.0 (12A366) 的 iOS 版本。我在运行 iOS 8.0.2 的设备(iPad Mini)上试了一下,效果很好。
  • 应该让_=试试? fm.removeItemAtURL(dstURL) 而不是 let _ = 试试? fileMgr.removeItemAtURL(dstURL)
  • 仅适用于简单的网站。如果您使用 ajax 或通过 angular 加载本地视图,则期望“仅 HTTP 支持跨源请求”。您唯一的后备是我不喜欢的本地网络服务器方法,因为它在本地网络上可见。这需要在帖子中注明,为人们节省一些时间。
【解决方案2】:

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/WKWebViewFIleUrlTestcopy files into /tmp/www and load them from there 演示。

如果您使用 Swift,您可以改为 try nachos4d's sample。 (它也比 shazron 的示例短得多,所以如果您在使用 shazron 的代码时遇到问题,请尝试一下。)

【讨论】:

  • 一种解决方法(此处提到:devforums.apple.com/message/1051027)是将内容移动到 tmp 并从那里访问它。我的快速测试似乎表明它确实有效......
  • 将文件移动到 /tmp 对我有用。但是……我的天,这是怎么通过测试的?
  • 那个 sample 代码太多,仅用于演示。要读取的文件必须在 /tmp/www/ 内。使用NSTemporaryDirectory()NSFileManager创建www目录(因为默认没有这个目录)。然后将你的文件复制到那里,现在阅读这个文件:)
  • 8.3 还没修复...?
  • 似乎在 9.0 中已修复
【解决方案3】:

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 项目的目录示例。

【讨论】:

    【解决方案4】:

    临时解决方法:我正在使用 GCDWebServer,正如 GuidoMB 所建议的那样。

    我首先找到捆绑的“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: 上启动/重启它。

    【讨论】:

    • 使用“服务器”可能会消耗WKWebView 提供的优于UIWebView 的任何性能优势。还不如坚持使用旧 API,直到解决此问题。
    • @ray 不使用单页应用程序。
    • 我也让 GCDWebServer 在我的应用程序的内部构建中工作。如果你的应用程序中有很多 javascript,那么服务器是完全值得的。但是有 other issues 阻止我现在使用 WKWebView,所以我希望在 iOS 9 中有所改进。
    • 以及如何在 WebView 上显示它?我真的不明白 GCDWebServer 是干什么用的?
    • 不是将 webview 指向“file://.....”,而是将其指向“http://localhost:/...”。
    【解决方案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]
    ];
    

    【讨论】:

    • 你也可以放 DIR 而不是 FILE。
    • 这是一个很棒的发现。我不知道为什么它的票数不高。
    • 这篇文章有更多信息(包括指向 webkit 源的链接):stackoverflow.com/questions/36013645/…
    • configuration.preferences setValue 在 iOS 9.3 上会崩溃
    • 我正在使用 iOS 13 SDK 但试图保持与 iOS 8 的兼容性,而 allowFileAccessFromFileURLs 方法与 NSUnknownKeyException 崩溃。
    【解决方案6】:

    除了 Dan Fabulich 提到的解决方案之外,XWebView 是另一种解决方法。 [WKWebView loadFileURL:allowingReadAccessToURL:]implemented through extension

    【讨论】:

    • 在查看了针对此问题的其他解决方法后,我决定使用 XWebView。 XWebView 是一个用 Swift 实现的框架,但我在 iOS 8 Objective-C 应用程序中使用它没有问题。
    【解决方案7】:

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

    这是nacho4d's solution 的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;
    }
    

    【讨论】:

    • 我无法让它在模拟器中的 iOS 8 上运行。我想在 /tmp/www 中放置一个 .png,然后在我的 html 中使用 img 标签。我应该为 img 标签 src 使用什么?
    • 正是我所需要的。谢谢!
    【解决方案8】:

    如果您尝试在较大的 HTML 字符串(例如:&lt;img src="file://..."&gt;)的中间显示本地图像,它仍然不会出现在设备上,因此我将图像文件加载到 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];
    

    【讨论】:

    • 在swift版本中,base64前请加分号。 result += "data:image/" + attachmentType + ";base64," + base64String
    • 谢谢,这是唯一对我有用的东西,因为我将 .gif 文件下载到设备上的临时文件夹中,然后将该文件作为 &lt;img&gt; 加载到 WKWebView在 HTML 字符串中。
    • @patrickd105 ,我对结果变量有一些疑问,您能否将代码与调用和调用函数一起发布,以便一切变得清晰
    【解决方案9】:

    我正在使用以下内容。我正在处理一些额外的东西,但您可以看到我在哪里注释掉了 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)")
    
        }
    
    }
    

    【讨论】:

    • 这似乎只适用于 HTML 文件,而不适用于其他资源。
    【解决方案10】:

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

    如果您的页面不复杂,您可以选择将页面设置为单页应用程序。

    也就是说,将所有资源嵌入到html文件中。

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

    这与设置 svg 图像的样式有点不同,但它不会阻碍你。

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

    【讨论】:

      【解决方案11】:

      在 GCDWebServer 的同一行中,我使用的是 SIMpleHttpServer (http://www.andyjamesdavies.com/blog/javascript/simple-http-server-on-mac-os-x-in-seconds),然后使用本地 url 加载请求。使用这种方法,您不必添加任何库,但网站文件不会包含在捆绑包中,因此无法交付。因此,这更适合 Debug 案例。

      【讨论】:

        【解决方案12】:

        我已经设法在 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()
        

        【讨论】:

          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

            尝试使用

            [webView loadHTMLString:htmlFileContent baseURL:baseURL];
            

            似乎它仍在工作。然而。

            【讨论】:

            • 谢谢,我试试看。
            • 这仅适用于 HTML 文件本身而不是资源,对吧?
            • 不幸的是,似乎只有 HTML 文件正在加载。我们希望这只是一个错误,而不是加载本地文件的新限制。我试图在 WebKit 源代码中找到这个错误。
            • 我遇到了同样的问题 - 加载了 HTML 文件,但未加载本地文件系统上的图像和其他资源。在控制台中,WebKit 抛出错误“不允许加载本地资源”。我为此提交了一个错误,雷达# 17835098
            猜你喜欢
            • 2018-12-26
            • 2017-05-24
            • 2016-04-05
            • 1970-01-01
            • 2018-01-29
            • 2018-01-19
            • 1970-01-01
            • 2016-04-08
            • 1970-01-01
            相关资源
            最近更新 更多