web-dev-qa-db-fra.com

WKWebView ne charge pas les fichiers locaux sous iOS 8

Pour les versions précédentes d'iOS 8, chargez une application Web locale (dans Bundle). Cela fonctionne correctement pour UIWebView et WKWebView, et j'ai même porté un jeu Web à l'aide de la nouvelle API WKWebView.

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

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

view.addSubview(webView)

Mais en version bêta 4, je viens d'avoir un écran blanc et vierge (UIWebView fonctionne 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 sandbox pour /

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

118
Lim Thye Chean

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

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

Pour iOS8 ~ iOS10 (Swift 3)

Comme la réponse de Dan Fabulish indique qu'il s'agit d'un bogue de WKWebView qui apparemment n'est pas résolu à tout moment et comme il a dit il y a un work-around :)

Je réponds juste parce que je voulais montrer le travail ici. Le code OMI indiqué entre https://github.com/shazron/WKWebViewFIleUrlTest regorge de détails sans rapport avec lesquels la plupart des gens ne sont probablement pas intéressés.

La solution consiste en 20 lignes de code, traitement des erreurs et commentaires inclus, pas besoin de 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)
        }
    }
}
100
nacho4d

WKWebView ne peut pas charger le contenu d'un fichier: URL via la méthode loadRequest:. 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 fera ce que vous voulez, [WKWebView loadFileURL:allowingReadAccessToURL:].

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

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

82
Dan Fabulich

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

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

enter image description here

Ensuite, utilisez un code qui ressemble à ceci (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">

Exemple de répertoire déplacé vers un projet xcode.

enter image description here

8
Markus T.

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

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

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

... puis lancez-le comme suit:

_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 blocage après le passage de l'application en arrière-plan, je l'ai donc arrêtée sur applicationDidEnterBackground: et applicationWillTerminate:; Je le démarre/le redémarre sur application:didFinishLaunching... et applicationWillEnterForeground:.

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

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

aussi cela semble bien fonctionner aussi ...

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

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

4
soflare

Je ne peux pas encore commenter, alors je poste ceci comme réponse séparée.

Ceci est une version objective-c de 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;
}
3
Faryar

Si vous essayez d'afficher une image locale au milieu d'une chaîne HTML plus grande, telle que: <img src="file://...">, elle n'apparaît toujours pas sur le périphérique. J'ai donc chargé le fichier image dans NSData et j'ai pu l'afficher. remplacer la chaîne src par les données elles-mêmes. Exemple de code permettant de créer la chaîne HTML à charger dans WKWebView, où résultat est ce qui remplacera ce qui se trouve entre les guillemets de src = "":

Swift:

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

Objective-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];
2
patrickd105

Pour qui doit résoudre ce problème sous iOS8:

Si votre page n'est pas compliquée, vous pouvez choisir de la transformer en application simple page.

En d'autres termes, incorporer toutes les ressources dans le fichier HTML.

Pour faire: 1. Copiez le contenu de votre fichier js/css dans/tags dans le fichier html respectivement; 2. convertissez vos fichiers image en svg pour remplacer le en conséquence. 3. chargez la page comme auparavant, 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 aient diminué un peu, mais il était digne de disposer d’une solution de contournement aussi simple sous iOS8/9/10.

1
firebear

J'utilise le ci-dessous. J'ai quelques éléments supplémentaires sur lesquels je travaille, mais vous pouvez voir où j'ai commenté le loadRequest et substituer 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)")

    }

}
1
Dustin Nielson

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

Si 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 ceci: enter image description here

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

la fonction fileURLForBuggyWKWebView8 est copiée à partir de @ 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
}
0
User9527

Dans la même ligne de GCDWebServer, j'utilise SImpleHttpServer ( http://www.andyjamesdavies.com/blog/javascript/simple-http-server-on-mac-os-x-in-seconds ) puis loadRequest avec l’URL localhost. Avec cette approche, vous n’aurez pas besoin d’ajouter de bibliothèque, mais les fichiers du site Web ne seront pas dans le paquet, ils ne seront donc pas livrables. De ce fait, cela serait plus approprié pour les cas de débogage.

0
toupper

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

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()
0
Patrick Smith