WKWebView ne charge pas les fichiers locaux sous iOS 8

123

Pour les versions bêta précédentes d'iOS 8, chargez une application Web locale (dans Bundle) et cela fonctionne bien pour les deux UIWebViewet WKWebView, et j'ai même porté un jeu Web à l'aide de la nouvelle WKWebViewAPI.

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

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

view.addSubview(webView)

Mais dans la bêta 4, je viens d'avoir un écran blanc vierge ( UIWebViewfonctionne toujours), on dirait que rien n'est chargé ou exécuté. J'ai vu une erreur dans le journal:

Impossible de créer une extension de bac à sable pour /

Une aide pour me guider dans la bonne direction? Merci!

Lim Thye Chean
la source
Essayez également d'ajouter le webView à la hiérarchie des vues dans viewDidLoad et chargez la demande dans viewWillAppear. Mon WKWebView fonctionne toujours, mais c'est comme ça que je l'ai. Peut-être que WebView a une optimisation pour ne pas charger les demandes si elles ne sont pas dans une hiérarchie de vues?
rvijay007
Terminé (le view.addView dans viewDidLoad et loadRequest dans viewWillAppear), et j'ai eu le même écran blanc et le même message d'erreur.
Lim Thye Chean
9
Cela ne semble se produire que sur l'appareil car le simulateur fonctionne bien. J'utilise Objc au fait.
GuidoMB
1
Cela reste un problème dans XCode 6 - beta 7. Ma solution temporaire était d'utiliser github.com/swisspol/GCDWebServer pour servir les fichiers locaux.
GuidoMB
2
Quelqu'un l'a testé sous iOS 8.0.1?
Lim Thye Chean

Réponses:

106

Ils ont finalement résolu le bug! Maintenant, nous pouvons utiliser -[WKWebView loadFileURL:allowingReadAccessToURL:]. Apparemment, le correctif valait quelques secondes dans la vidéo de la WWDC 2015 504 Présentation de Safari View Controller

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

Pour iOS8 ~ iOS10 (Swift 3)

Comme l' indique la réponse de Dan Fabulish, il s'agit d'un bogue de WKWebView qui n'est apparemment pas résolu de sitôt et comme il l'a dit, il y a une solution :)

Je réponds simplement parce que je voulais montrer le contournement ici. Le code IMO affiché sur https://github.com/shazron/WKWebViewFIleUrlTest regorge de détails sans rapport avec la plupart des gens ne sont probablement pas intéressés.

Le contournement est de 20 lignes de code, gestion des erreurs et commentaires inclus, pas besoin d'un serveur :)

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
}

Et peut être utilisé comme:

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
la source
2
Quelques modifications pour qu'il copie le dossier entier, les images et tout. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf
1
La solution de Piwaf fonctionnait légèrement mieux pour moi, notez qu'elle devrait être "self.webView" et non "self.loadingWebView" étant donné l'exemple ci-dessus
Patrick Fabrizius
2
Merci @ nacho4d. tldr; la solution de dossier / tmp ne fonctionnera pas sur 8.0 mais fonctionnera sur 8.0.2. J'avais du mal à faire fonctionner cette solution sur mon appareil de test et j'ai finalement cloné le repo de shazron pour l'essayer. Cela n'a pas fonctionné non plus. Il s'avère que la solution de shazron ne fonctionne pas sur la version d'iOS que mon appareil exécutait 8.0 (12A366) sur iPhone 6 Plus. Je l'ai essayé sur un appareil (iPad Mini) exécutant iOS 8.0.2 et cela fonctionne bien.
jvoll
1
il devrait être laissé _ = essayer? fm.removeItemAtURL (dstURL) au lieu de let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Uniquement pour les sites Web simples. Si vous utilisez ajax ou chargez des vues locales via angular, attendez-vous à ce que "les requêtes d'origine croisée ne soient prises en charge que pour HTTP". Votre seule solution de rechange est l'approche du serveur Web local que je n'aime pas car elle est visible sur le réseau local. Cela doit être noté dans le post, économisez quelques heures aux gens.
jenson-button-event
83

WKWebView ne peut pas charger le contenu du fichier: URL via sa loadRequest:méthode. http://www.openradar.me/18039024

Vous pouvez charger du contenu via loadHTMLString:, mais si votre baseURL est un fichier: URL, cela ne fonctionnera toujours pas.

iOS 9 a une nouvelle API qui va faire ce que vous voulez, [WKWebView loadFileURL:allowingReadAccessToURL:] .

Il existe une solution de contournement pour iOS 8 , démontrée par shazron dans Objective-C ici https://github.com/shazron/WKWebViewFIleUrlTest pour copier des fichiers /tmp/wwwet les charger à partir de là .

Si vous travaillez dans Swift, vous pouvez essayer l'exemple de nachos4d à la place. (Il est également beaucoup plus court que l'exemple de shazron, donc si vous rencontrez des problèmes avec le code de shazron, essayez plutôt.)

Dan Fabulich
la source
1
Une solution de contournement (mentionnée ici: devforums.apple.com/message/1051027 ) consiste à déplacer le contenu dans tmp et à y accéder à partir de là. Mon test rapide semble indiquer que cela fonctionne ...
Mike M
6
Non corrigé dans 8.1.
mat
1
Déplacer les fichiers vers / tmp fonctionne pour moi. Mais ... mon dieu, comment cela a-t-il été testé?
Greg Maletic
1
Cet exemple est beaucoup trop de code juste pour une démo. Le fichier à lire doit être à l'intérieur /tmp/www/. Utilisez NSTemporaryDirectory()et NSFileManagerpour créer un wwwrépertoire (car il n'y en a pas par défaut). Ensuite, copiez votre fichier là-dedans et maintenant lisez ce fichier :)
nacho4d
2
8.3 et ce n'est toujours pas corrigé ...?
ninjaneer
8

Un exemple d'utilisation de [WKWebView loadFileURL: allowReadAccessToURL:] sur iOS 9 .

Lorsque vous déplacez le dossier Web vers un projet, sélectionnez "Créer des références de dossier"

entrez la description de l'image ici

Ensuite, utilisez un code similaire à celui-ci (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)
  }
}

Dans le fichier html, utilisez des chemins de fichiers comme celui-ci

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

pas comme ça

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

Un exemple de répertoire déplacé vers un projet xcode.

entrez la description de l'image ici

Markus T.
la source
6

Solution temporaire: j'utilise GCDWebServer , comme suggéré par GuidoMB.

Je trouve d'abord le chemin de mon dossier "www /" (qui contient un "index.html"):

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

... puis démarrez-le comme ceci:

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

Pour l'arrêter:

[_webServer stop];
_webServer = nil;

Les performances semblent bonnes, même sur un iPad 2.


J'ai remarqué un plantage après que l'application soit passée en arrière-plan, alors je l'ai arrêtée applicationDidEnterBackground:et applicationWillTerminate:; Je le démarre / le redémarre sur application:didFinishLaunching...et applicationWillEnterForeground:.

EthanB
la source
1
L'utilisation d'un «serveur» consomme probablement tous les avantages de performance que cela WKWebViewprocure UIWebView. Autant s'en tenir à l'ancienne API jusqu'à ce que cela soit corrigé.
ray
@ray Pas avec une application à page unique.
EthanB
J'ai également fait fonctionner GCDWebServer, dans les versions internes de mon application. Et s'il y a beaucoup de javascript dans votre application, le serveur en vaut vraiment la peine. Mais il y a d' autres problèmes qui m'empêchent d'utiliser WKWebView pour le moment, j'espère donc des améliorations dans iOS 9.
Tom Hamming
Et comment le montrer sur une WebView? Je ne comprends vraiment pas à quoi sert GCDWebServer?
chipbk10
1
Au lieu de pointer la vue Web vers "file: // .....", vous la pointez vers "http: // localhost: <port> / ...".
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Cela a résolu le problème pour moi iOS 8.0+ dev.apple.com

cela semble aussi bien fonctionner aussi ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
la source
au lieu de FILE, vous pouvez également mettre DIR.
nullqube
C'est une bonne trouvaille. Je ne sais pas pourquoi il n'a pas été voté plus haut.
plivesey
Cet article contient plus d'informations (y compris des liens vers la source du
kit Web
configuration.preferences setValue donnera un crash sur iOS 9.3
Vignesh Kumar
J'utilise le SDK iOS 13 mais j'essaie de garder la compatibilité avec iOS 8, et l' allowFileAccessFromFileURLsapproche se bloque avec NSUnknownKeyException.
arlomedia
4

Outre les solutions mentionnées par Dan Fabulich, XWebView est une autre solution de contournement. [WKWebView loadFileURL: allowReadAccessToURL:] est implémenté via l'extension .

soflare
la source
1
J'ai décidé d'utiliser XWebView après avoir examiné d'autres solutions de contournement pour ce problème. XWebView est un framework implémenté dans Swift mais je n'ai eu aucun problème à l'utiliser dans mon application iOS 8 Objective-C.
chmaynard
4

Je ne peux pas encore commenter, donc je publie ceci comme une réponse distincte.

Ceci est une version objective-c de la solution de nacho4d . La meilleure solution de contournement que j'ai vue jusqu'à présent.

- (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
la source
1
Je ne peux pas faire fonctionner cela sur iOS 8 dans le simulateur. Je veux mettre un .png dans / tmp / www, puis utiliser la balise img dans mon html. Que dois-je utiliser pour la balise img src?
user3246173
était exactement ce dont j'avais besoin. Merci!
Tinkerbell
3

Dans le cas où vous essayez d'afficher une image locale au milieu d'une chaîne HTML plus grande comme <img src="file://...">:, elle n'apparaît toujours pas sur l'appareil, j'ai donc chargé le fichier image dans NSData et j'ai pu l'afficher en remplaçant la chaîne src par les données elles-mêmes. Exemple de code pour aider à créer la chaîne HTML à charger dans WKWebView, où le résultat est ce qui remplacera ce qui se trouve à l'intérieur des guillemets de src = "":

Rapide:

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

Objectif 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
la source
dans la version swift, veuillez ajouter un point-virgule avant base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil
Merci, c'est la seule chose qui a fonctionné pour moi, car je télécharge un fichier .gif dans un dossier temporaire sur l'appareil, puis charge ce fichier dans le WKWebViewsous forme <img>de chaîne HTML.
M. Zystem
1

J'utilise le ci-dessous. A des trucs supplémentaires sur lesquels je travaille, mais vous pouvez voir où j'ai commenté le loadRequest et je remplace l'appel loadHTMLString. J'espère que cela aidera jusqu'à ce qu'ils corrigent le bogue.

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
la source
3
Cela semble fonctionner uniquement pour les fichiers HTML mais pas pour les autres ressources.
Lim Thye Chean
1

Pour qui doit contourner ce problème sous iOS8:

Si votre page n'est pas compliquée, vous pouvez choisir de faire de la page une application à page unique.

En d'autres termes, pour intégrer toutes les ressources dans le fichier html.

Pour faire: 1. Copiez respectivement le contenu de votre fichier js / css dans / tags dans le fichier html; 2. convertissez vos fichiers image en svg pour les remplacer en conséquence. 3. chargez la page comme avant, en utilisant [webView loadHTMLString: baseURL:], par exemple

C'était un peu différent du style d'une image svg, mais cela ne devrait pas vous bloquer autant.

Il semblait que les performances de rendu de la page diminuaient un peu, mais il valait la peine d'avoir une solution de contournement aussi simple sous iOS8 / 9/10.

ours de feu
la source
0

J'ai réussi à utiliser le serveur Web de PHP sur OS X. La copie dans le répertoire temporaire / www n'a pas fonctionné pour moi. Le Python SimpleHTTPServer s'est plaint de vouloir lire les types MIME, probablement un problème de sandboxing.

Voici un serveur utilisant 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
la source
0

La solution @ nacho4d est bonne. Je veux le changer un peu mais je ne sais pas comment le changer dans votre message. Alors je l'ai mis ici, j'espère que cela ne vous dérange pas. Merci.

Dans le cas où vous avez un dossier www, il y a beaucoup d'autres fichiers tels que png, css, js etc. Ensuite, vous devez copier tous les fichiers dans le dossier tmp / www. par exemple, vous avez un dossier www comme celui-ci: entrez la description de l'image ici

puis dans 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)")
        }
    }
}

le fichier de fonctionURLForBuggyWKWebView8 est copié depuis @ 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
}
DàChún
la source
-1

Essayez d'utiliser

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Il semble que cela fonctionne toujours. Encore.

Oleksii
la source
Merci, je vais l'essayer.
Lim Thye Chean
3
Cela ne fonctionne que pour le fichier HTML lui-même, pas pour les ressources, non?
Lim Thye Chean
1
Malheureusement oui, seul le fichier HTML semble se charger. Espérons que ce n'est qu'un bug, et non une nouvelle restriction pour charger les fichiers locaux. J'essaye de trouver ce bogue dans les sources de WebKit.
Oleksii
1
J'ai rencontré le même problème - les fichiers HTML sont chargés mais les images et autres ressources qui se trouvent sur le système de fichiers local ne sont pas chargées. Dans la console, WebKit renvoie une erreur "non autorisé à charger la ressource locale". J'ai déposé un bug pour cela, radar # 17835098
mszaro