WKWebView lädt keine lokalen Dateien unter iOS 8

123

Laden Sie für frühere iOS 8-Betas eine lokale Web-App (im Bundle) und sie funktioniert für beide UIWebViewund WKWebView, und ich habe sogar ein Web-Spiel mit der neuen portiertWKWebView API .

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

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

view.addSubview(webView)

Aber in Beta 4 habe ich gerade einen leeren weißen Bildschirm bekommen (UIWebView funktioniert immer noch), es sieht so aus, als ob nichts geladen oder ausgeführt wird. Ich habe einen Fehler im Protokoll gesehen:

Es konnte keine Sandbox-Erweiterung für erstellt werden /

Irgendeine Hilfe, um mich in die richtige Richtung zu führen? Vielen Dank!

Lim Thye Chean
quelle
Versuchen Sie auch, die Webansicht der Ansichtshierarchie in viewDidLoad hinzuzufügen, und laden Sie die Anforderung in viewWillAppear. Mein WKWebView funktioniert immer noch, aber so habe ich es. Vielleicht hat das WebView eine Optimierung, um Anforderungen nicht zu laden, wenn sie nicht in einer Ansichtshierarchie sind?
rvijay007
Fertig (view.addView in viewDidLoad und loadRequest in viewWillAppear), und ich habe den gleichen weißen Bildschirm und die gleiche Fehlermeldung erhalten.
Lim Thye Chean
9
Dies scheint nur auf dem Gerät zu passieren, da der Simulator einwandfrei funktioniert. Ich benutze übrigens Objc.
GuidoMB
1
Dies bleibt ein Problem in XCode 6 - Beta 7. Meine vorübergehende Lösung bestand darin, github.com/swisspol/GCDWebServer zum Bereitstellen lokaler Dateien zu verwenden.
GuidoMB
2
Hat es jemand unter iOS 8.0.1 getestet?
Lim Thye Chean

Antworten:

106

Sie haben endlich den Fehler behoben! Jetzt können wir verwenden -[WKWebView loadFileURL:allowingReadAccessToURL:]. Anscheinend war das Update in WWDC 2015 Video 504 Einführung in Safari View Controller einige Sekunden wert

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

Für iOS8 ~ iOS10 (Swift 3)

Wie Dan Fabulishs Antwort besagt, handelt es sich um einen Fehler von WKWebView, der anscheinend nicht in Kürze behoben werden kann und wie er sagte, gibt es eine :)

Ich antworte nur, weil ich hier die Problemumgehung zeigen wollte. Der IMO-Code unter https://github.com/shazron/WKWebViewFIleUrlTest enthält viele nicht verwandte Details, an denen die meisten Menschen wahrscheinlich nicht interessiert sind.

Die Problemumgehung besteht aus 20 Codezeilen, Fehlerbehandlung und Kommentaren, kein Server erforderlich :)

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
}

Und kann verwendet werden als:

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)
        }
    }
}
nacho4d
quelle
2
Einige Änderungen, um den gesamten Ordner, die Bilder und alles zu kopieren. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf
1
Piwafs Lösung funktionierte etwas besser für mich. Beachten Sie, dass es im obigen Beispiel "self.webView" und nicht "self.loadingWebView" sein sollte
Patrick Fabrizius
2
Danke @ nacho4d. tldr; Die Ordnerlösung / tmp funktioniert nicht unter 8.0, sondern unter 8.0.2. Ich hatte Probleme, diese Lösung auf meinem Testgerät zum Laufen zu bringen, und klonte schließlich Shazrons Repo, um es auszuprobieren. Das hat auch nicht funktioniert. Es stellt sich heraus, dass die Lösung von shazron unter iOS nicht funktioniert. Auf meinem Gerät wurde 8.0 (12A366) auf dem iPhone 6 Plus ausgeführt. Ich habe es auf einem Gerät (iPad Mini) mit iOS 8.0.2 versucht und es funktioniert einwandfrei.
jvoll
1
es sollte _ = versuchen lassen? fm.removeItemAtURL (dstURL) statt _ = versuchen lassen? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Nur für einfache Websites. Wenn Sie Ajax verwenden oder lokale Ansichten über Winkel laden, erwarten Sie "Ursprungsübergreifende Anforderungen werden nur für HTTP unterstützt". Ihr einziger Fallback ist der lokale Webserver-Ansatz, den ich nicht mag, da er im lokalen Netzwerk sichtbar ist. Dies muss in der Post notiert werden, sparen Sie einige Stunden.
Jenson-Button-Event
83

WKWebView kann über seine loadRequest:Methode keinen Inhalt aus Datei: URLs laden . http://www.openradar.me/18039024

Sie können Inhalte über laden loadHTMLString:, aber wenn Ihre baseURL eine Datei: URL ist, funktioniert sie immer noch nicht.

iOS 9 verfügt über eine neue API, die das tut, was Sie wollen [WKWebView loadFileURL:allowingReadAccessToURL:] .

Es gibt eine Problemumgehung für iOS 8 , die von shazron in Objective-C unter https://github.com/shazron/WKWebViewFIleUrlTest demonstriert wurde, um Dateien in /tmp/wwwdiese zu kopieren und von dort zu laden .

Wenn Sie in Swift arbeiten, können Sie stattdessen das Beispiel von nachos4d ausprobieren . (Es ist auch viel kürzer als Shazrons Beispiel. Wenn Sie also Probleme mit Shazrons Code haben, probieren Sie es stattdessen aus.)

Dan Fabulich
quelle
1
Eine Problemumgehung (hier erwähnt: devforums.apple.com/message/1051027 ) besteht darin, Inhalte in tmp zu verschieben und von dort aus darauf zuzugreifen. Mein schneller Test scheint anzuzeigen, dass es funktioniert ...
Mike M
6
Nicht behoben in 8.1.
Matt
1
Das Verschieben der Dateien nach / tmp funktioniert bei mir. Aber ... mein Gott, wie kam das durch das Testen?
Greg Maletic
1
Dieses Beispiel ist viel zu viel Code nur für eine Demo. Die zu lesende Datei muss sich im Inneren befinden /tmp/www/. Verwenden Sie NSTemporaryDirectory()und NSFileManager, um ein wwwVerzeichnis zu erstellen (da es standardmäßig kein solches Verzeichnis gibt). Dann kopieren Sie Ihre Datei dort und lesen Sie jetzt diese Datei :)
nacho4d
2
8.3 und es ist immer noch nicht behoben ...?
Ninjaneer
8

Ein Beispiel für die Verwendung von [WKWebView loadFileURL: allowReadAccessToURL:] unter iOS 9 .

Wenn Sie den Webordner in ein Projekt verschieben, wählen Sie "Ordnerreferenzen erstellen".

Geben Sie hier die Bildbeschreibung ein

Verwenden Sie dann Code, der ungefähr so ​​aussieht (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)
  }
}

Verwenden Sie in der HTML-Datei folgende Dateipfade

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

so nicht

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

Ein Beispiel für ein Verzeichnis, das in ein xcode-Projekt verschoben wird.

Geben Sie hier die Bildbeschreibung ein

Markus T.
quelle
6

Temporäre Problemumgehung: Ich verwende GCDWebServer , wie von GuidoMB vorgeschlagen.

Ich finde zuerst den Pfad meines gebündelten Ordners "www /" (der eine "index.html" enthält):

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

... dann starte es so:

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

Um es zu stoppen:

[_webServer stop];
_webServer = nil;

Die Leistung scheint auch auf einem iPad 2 in Ordnung zu sein.


Ich habe einen Absturz bemerkt, nachdem die App in den Hintergrund getreten ist, also stoppe ich sie auf applicationDidEnterBackground:und applicationWillTerminate:; Ich starte / starte es auf application:didFinishLaunching...und neu applicationWillEnterForeground:.

EthanB
quelle
1
Die Verwendung eines "Servers" verschlingt wahrscheinlich alle Leistungsvorteile, WKWebViewdie sich daraus ergeben UIWebView. Könnte auch bei der alten API bleiben, bis dies behoben ist.
Ray
@ray Nicht mit einer Single-Page-App.
EthanB
Ich habe GCDWebServer auch in internen Builds meiner App zum Laufen gebracht. Und wenn Ihre App viel Javascript enthält, ist der Server es absolut wert. Aber es gibt andere Probleme , die mich derzeit daran hindern, WKWebView zu verwenden, und ich hoffe auf Verbesserungen in iOS 9.
Tom Hamming
Und wie kann man es in einem WebView anzeigen? Ich verstehe wirklich nicht, wofür GCDWebServer ist?
Chipbk10
1
Anstatt die Webansicht auf "file: // ....." zu verweisen, verweisen Sie auf "http: // localhost: <port> / ...".
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Dies löste das Problem für mich iOS 8.0+ dev.apple.com

auch das scheint gut zu funktionieren ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
quelle
Anstelle von DATEI können Sie auch DIR setzen.
nullqube
Dies ist ein großartiger Fund. Ich weiß nicht, warum es nicht höher gewählt wird.
Plivesey
Dieser Beitrag hat mehr Informationen (einschließlich Links zur Webkit-Quelle): stackoverflow.com/questions/36013645/…
plivesey
configuration.preferences setValue wird unter iOS 9.3
Vignesh Kumar
Ich verwende das iOS 13 SDK, versuche jedoch, die Kompatibilität mit iOS 8 aufrechtzuerhalten, und der allowFileAccessFromFileURLsAnsatz stürzt ab NSUnknownKeyException.
Arlomedia
4

Neben den von Dan Fabulich erwähnten Lösungen ist XWebView eine weitere Problemumgehung . [WKWebView loadFileURL: allowReadAccessToURL:] wird durch Erweiterung implementiert .

Soflare
quelle
1
Ich habe mich für XWebView entschieden, nachdem ich mir andere Problemumgehungen für dieses Problem angesehen hatte. XWebView ist ein in Swift implementiertes Framework, aber ich hatte kein Problem damit, es in meiner iOS 8 Objective-C-App zu verwenden.
Chmaynard
4

Ich kann noch keinen Kommentar abgeben, daher poste ich dies als separate Antwort.

Dies ist eine Objective-C-Version der nacho4d-Lösung . Die beste Problemumgehung, die ich bisher gesehen habe.

- (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;
}
Faryar
quelle
1
Ich kann dies unter iOS 8 im Simulator nicht zum Laufen bringen. Ich möchte ein .png in / tmp / www einfügen und dann das img-Tag in meinem HTML verwenden. Was soll ich für das img tag src verwenden?
user3246173
war genau das, was ich brauchte. Vielen Dank!
Tinkerbell
3

Für den Fall, dass Sie versuchen, ein lokales Bild in der Mitte einer größeren HTML-Zeichenfolge wie: anzuzeigen <img src="file://...">, wird es immer noch nicht auf dem Gerät angezeigt. Daher habe ich die Bilddatei in NSData geladen und konnte sie anzeigen, indem ich die src-Zeichenfolge durch ersetzte die Daten selbst. Beispielcode zum Erstellen der HTML-Zeichenfolge zum Laden in WKWebView. Das Ergebnis ersetzt das, was in den Anführungszeichen von src = "" enthalten ist:

Schnell:

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

Ziel 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];
patrickd105
quelle
Fügen Sie in der schnellen Version vor base64 ein Semikolon hinzu. result += "data:image/" + attachmentType + ";base64," + base64String
Shahil
Vielen Dank, dies ist das einzige, was für mich funktioniert hat, da ich eine GIF-Datei in einen temporären Ordner auf dem Gerät herunterlade und diese Datei dann WKWebViewals <img>HTML-Zeichenfolge in den Ordner lade.
Mr. Zystem
1

Ich benutze das unten. Ich arbeite an einigen zusätzlichen Dingen, aber Sie können sehen, wo ich die loadRequest auskommentiert habe und den loadHTMLString-Aufruf ersetze. Hoffe das hilft bis sie den Fehler behoben haben.

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

    }

}
Dustin Nielson
quelle
3
Dies scheint nur für die HTML-Dateien zu funktionieren, nicht jedoch für andere Ressourcen.
Lim Thye Chean
1

Für wen muss dieses Problem unter iOS8 umgehen:

Wenn Ihre Seite nicht kompliziert ist, können Sie die Seite als Einzelseitenanwendung erstellen.

Mit anderen Worten, um alle Ressourcen in die HTML-Datei einzubetten.

So gehen Sie vor: 1. Kopieren Sie den Inhalt Ihrer js / css-Datei in / tags in der HTML-Datei. 2. konvertieren Sie Ihre Bilddateien in svg, um die entsprechenden zu ersetzen. 3. Laden Sie die Seite wie zuvor, indem Sie beispielsweise [webView loadHTMLString: baseURL:] verwenden

Es war ein bisschen anders als das Stylen eines SVG-Bildes, aber es sollte Sie nicht so sehr blockieren.

Es schien, dass die Leistung beim Rendern von Seiten etwas abnahm, aber es war wert, eine so einfache Problemumgehung unter iOS8 / 9/10 zu haben.

Feuerbär
quelle
0

Ich habe es geschafft, den PHP-Webserver unter OS X zu verwenden. Das Kopieren in das temporäre Verzeichnis / www hat bei mir nicht funktioniert. Der Python SimpleHTTPServer beschwerte sich darüber, dass er MIME-Typen lesen wollte, wahrscheinlich ein Sandbox-Problem.

Hier ist ein Server mit 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()
Patrick Smith
quelle
0

@ Nacho4d Lösung ist gut. Ich möchte es ein wenig ändern, aber ich weiß nicht, wie ich es in Ihrem Beitrag ändern soll. Also habe ich es hierher gebracht, ich hoffe es macht dir nichts aus. Vielen Dank.

Wenn Sie einen WWW-Ordner haben, gibt es viele andere Dateien wie PNG, CSS, JS usw. Dann müssen Sie alle Dateien in den Ordner tmp / www kopieren. Sie haben beispielsweise einen WWW-Ordner wie diesen: Geben Sie hier die Bildbeschreibung ein

dann in 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)")
        }
    }
}

Die Funktion fileURLForBuggyWKWebView8 wird von @ nacho4d kopiert:

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
}
DàChún
quelle
-1

Versuchen Sie es mit

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Scheint, es funktioniert immer noch. Noch.

Oleksii
quelle
Danke, ich werde es ausprobieren.
Lim Thye Chean
3
Dies funktioniert nur für die HTML-Datei selbst, nicht für die Ressourcen, oder?
Lim Thye Chean
1
Leider scheint nur die HTML-Datei geladen zu werden. Hoffen wir, dass es nur ein Fehler ist und keine neue Einschränkung beim Laden lokaler Dateien. Ich versuche, diesen Fehler in den WebKit-Quellen zu finden.
Oleksii
1
Ich bin auf dasselbe Problem gestoßen - HTML-Dateien werden geladen, aber Bilder und andere Ressourcen, die sich im lokalen Dateisystem befinden, werden nicht geladen. In der Konsole gibt WebKit den Fehler "Lokale Ressource darf nicht geladen werden" aus. Ich habe einen Fehler dafür eingereicht, Radar # 17835098
mszaro