WKWebView non carica i file locali sotto iOS 8


123

Per le versioni beta di iOS 8 precedenti, carica un'app Web locale (in bundle) e funziona bene per entrambi UIWebViewe WKWebView, e ho persino portato un gioco Web utilizzando la nuova WKWebViewAPI.

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

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

view.addSubview(webView)

Ma nella beta 4, ho appena ricevuto uno schermo bianco vuoto ( UIWebViewfunziona ancora), sembra che non sia stato caricato o eseguito nulla. Ho visto un errore nel registro:

Impossibile creare un'estensione sandbox per /

Qualche aiuto per guidarmi nella giusta direzione? Grazie!


Prova anche ad aggiungere webView alla gerarchia di visualizzazione in viewDidLoad e carica la richiesta in viewWillAppear. Il mio WKWebView funziona ancora, ma è così che ce l'ho. Forse il WebView ha un'ottimizzazione per non caricare le richieste se non sono in una gerarchia di visualizzazione?
rvijay007

Fatto (il view.addView in viewDidLoad e loadRequest in viewWillAppear), e ho ottenuto la stessa schermata bianca e lo stesso messaggio di errore.
Lim Thye Chean

9
Questo sembra accadere solo sul dispositivo perché sul simulatore funziona bene. A proposito, sto usando Objc.
GuidoMB

1
Questo rimane un problema in XCode 6 - beta 7. La mia soluzione temporanea era usare github.com/swisspol/GCDWebServer per servire i file locali.
GuidoMB

2
Qualcuno l'ha testato con iOS 8.0.1?
Lim Thye Chean

Risposte:


106

Finalmente hanno risolto il bug! Ora possiamo usare -[WKWebView loadFileURL:allowingReadAccessToURL:]. Apparentemente la correzione valeva alcuni secondi nel video 504 del WWDC 2015 Presentazione del controller di visualizzazione di Safari

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

Per iOS8 ~ iOS10 (Swift 3)

Come afferma la risposta di Dan Fabulish, questo è un bug di WKWebView che apparentemente non verrà risolto presto e come ha detto c'è una soluzione :)

Rispondo solo perché volevo mostrare il work-around qui. Il codice IMO mostrato in https://github.com/shazron/WKWebViewFIleUrlTest è pieno di dettagli non correlati a cui la maggior parte delle persone probabilmente non è interessata.

Il work-around è di 20 righe di codice, gestione degli errori e commenti inclusi, non è necessario un server :)

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
}

E può essere utilizzato come:

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
Alcune modifiche per copiare l'intera cartella, immagini e tutto il resto. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf

1
La soluzione di Piwaf ha funzionato leggermente meglio per me, nota che dovrebbe essere "self.webView" e non "self.loadingWebView" dato l'esempio sopra però
Patrick Fabrizius

2
Grazie @ nacho4d. TLDR; la soluzione della cartella / tmp non funzionerà su 8.0 ma funzionerà su 8.0.2. Ho avuto problemi a far funzionare questa soluzione sul mio dispositivo di prova e alla fine ho clonato il repository di shazron per provarlo. Neanche questo ha funzionato. Si scopre che la soluzione di shazron non funziona sulla versione di iOS sul mio dispositivo in esecuzione 8.0 (12A366) su iPhone 6 Plus. L'ho provato su un dispositivo (iPad Mini) con iOS 8.0.2 e funziona bene.
jvoll

1
dovrebbe essere lasciato _ = provare? fm.removeItemAtURL (dstURL) invece di let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún

3
Solo per siti web semplici. Se stai usando ajax o stai caricando viste locali tramite angular, aspettati che "le richieste cross origin sono supportate solo per HTTP". Il tuo unico fallback è l'approccio del server web locale che non mi piace poiché è visibile sulla rete locale. Questo deve essere annotato nel post, salva la gente alcune ore.
evento jenson-button

83

WKWebView non può caricare contenuti da file: URL tramite il suo loadRequest:metodo. http://www.openradar.me/18039024

Puoi caricare il contenuto tramite loadHTMLString:, ma se il tuo baseURL è un file: URL, non funzionerà comunque.

iOS 9 ha una nuova API che fare quello che vuoi, [WKWebView loadFileURL:allowingReadAccessToURL:] .

C'è una soluzione alternativa per iOS 8 , dimostrata da shazron in Objective-C qui https://github.com/shazron/WKWebViewFIleUrlTest per copiare i file /tmp/wwwe caricarli da lì .

Se stai lavorando in Swift, potresti invece provare l'esempio di nachos4d . (È anche molto più breve del campione di shazron, quindi se hai problemi con il codice di shazron, provalo.)


1
Una soluzione alternativa (menzionata qui: devforums.apple.com/message/1051027 ) è spostare il contenuto in tmp e accedervi da lì. Il mio rapido test sembra indicare che funziona ...
Mike M

6
Non risolto in 8.1.
matt

1
Spostare i file in / tmp funziona per me. Ma ... mio Dio, come ha fatto a superare i test?
Greg Maletic

1
Questo campione è troppo codice molto solo per una demo. Il file da leggere deve essere al suo interno /tmp/www/. Utilizzare NSTemporaryDirectory()e NSFileManagerper creare una wwwdirectory (perché non esiste una directory di questo tipo per impostazione predefinita). Quindi copia il tuo file lì e ora leggi questo file :)
nacho4d

2
8.3 e non è ancora stato risolto ...?
Ninjaneer

8

Un esempio di come utilizzare [WKWebView loadFileURL: allowReadAccessToURL:] su iOS 9 .

Quando sposti la cartella web in un progetto, seleziona "Crea riferimenti cartella"

inserisci qui la descrizione dell'immagine

Quindi usa un codice simile a questo (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)
  }
}

Nel file html usa percorsi di file come questo

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

non così

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

Un esempio di directory spostata in un progetto xcode.

inserisci qui la descrizione dell'immagine


6

Soluzione temporanea: sto utilizzando GCDWebServer , come suggerito da GuidoMB.

Per prima cosa trovo il percorso della mia cartella "www /" in bundle (che contiene un "index.html"):

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

... quindi avvialo in questo modo:

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

Per fermarlo:

[_webServer stop];
_webServer = nil;

Le prestazioni sembrano buone, anche su un iPad 2.


Ho notato un arresto anomalo dopo che l'app è passata in background, quindi lo interrompo su applicationDidEnterBackground:e applicationWillTerminate:; Lo avvio / riavvio su application:didFinishLaunching...e applicationWillEnterForeground:.


1
L'uso di un "server" probabilmente consuma tutti i vantaggi in termini di prestazioni che WKWebViewfornisce UIWebView. Tanto vale restare con la vecchia API finché il problema non viene risolto.
raggio

@ray Non con un'app a pagina singola.
EthanB

Ho fatto funzionare anche GCDWebServer, nelle build interne della mia app. E se nella tua app c'è molto javascript, il server ne vale assolutamente la pena. Ma ci sono altri problemi che mi impediscono di utilizzare WKWebView in questo momento, quindi spero in miglioramenti in iOS 9.
Tom Hamming

E come mostrarlo su una WebView? Davvero non capisco a cosa serve GCDWebServer?
chipbk10

1
Invece di puntare il webview a "file: // .....", lo punti a "http: // localhost: <port> / ...".
EthanB

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

Questo ha risolto il problema per me iOS 8.0+ dev.apple.com

anche questo sembra funzionare benissimo anche ...

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

invece di FILE puoi mettere anche DIR.
nullqube

Questa è una grande scoperta. Non so perché non sia votato più in alto.
plivesey

Questo post ha più informazioni (compresi i collegamenti ai fonte webkit): stackoverflow.com/questions/36013645/...
plivesey

configuration.preferences setValue darà crash su iOS 9.3
Vignesh Kumar

Sto usando l'SDK di iOS 13 ma cerco di mantenere la compatibilità con iOS 8 e l' allowFileAccessFromFileURLsapproccio si blocca con NSUnknownKeyException.
arlomedia

4

Oltre alle soluzioni menzionate da Dan Fabulich, XWebView è un'altra soluzione alternativa. [WKWebView loadFileURL: allowReadAccessToURL:] è implementato tramite l'estensione .


1
Ho deciso di utilizzare XWebView dopo aver esaminato altre soluzioni per questo problema. XWebView è un framework implementato in Swift ma non ho avuto problemi a usarlo nella mia app Objective-C per iOS 8.
chmaynard

4

Non posso ancora commentare, quindi lo inserisco come risposta separata.

Questa è una versione obiettivo-c della soluzione di nacho4d . La migliore soluzione alternativa che ho visto finora.

- (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
Non riesco a farlo funzionare su iOS 8 nel simulatore. Voglio inserire un .png in / tmp / www e quindi utilizzare il tag img nel mio html. Cosa dovrei usare per il tag img src?
user3246173

era esattamente quello di cui avevo bisogno. Grazie!
Tinkerbell

3

Nel caso in cui si stia tentando di visualizzare un'immagine locale nel mezzo di una stringa HTML più grande come <img src="file://...">:, non viene ancora visualizzata sul dispositivo, quindi ho caricato il file immagine in NSData e sono riuscito a visualizzarlo sostituendo la stringa src con i dati stessi. Codice di esempio per aiutare a costruire la stringa HTML da caricare in WKWebView, dove il risultato è ciò che sostituirà il contenuto delle virgolette di 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];

nella versione swift, aggiungi un punto e virgola prima di base64. result += "data:image/" + attachmentType + ";base64," + base64String
Shahil

Grazie, questa è l'unica cosa che ha funzionato per me, perché scarico un file .gif in una cartella temporanea sul dispositivo, quindi carico quel file WKWebViewcome <img>all'interno di una stringa HTML.
Mr. Zystem

1

Sto usando il sotto. Ci sono alcune cose extra su cui sto lavorando, ma puoi vedere dove ho commentato loadRequest e sto sostituendo la chiamata loadHTMLString. Spero che questo aiuti fino a quando non risolveranno il bug.

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
Sembra funzionare solo per i file HTML ma non per altre risorse.
Lim Thye Chean

1

Per chi deve risolvere questo problema con iOS8:

Se la tua pagina non è complicata, potresti scegliere di creare la pagina come applicazione a pagina singola.

In altre parole, per incorporare tutte le risorse nel file html.

Per fare: 1. copiare il contenuto del file js / css rispettivamente in / tags nel file html; 2. converti i tuoi file immagine in svg per sostituire il file di conseguenza. 3. caricare la pagina come prima, utilizzando [webView loadHTMLString: baseURL:], ad esempio

Era un po 'diverso dallo stile di un'immagine svg, ma non dovrebbe bloccarti così tanto.

Sembrava che le prestazioni di rendering della pagina fossero leggermente diminuite, ma era degno di avere una soluzione così semplice funzionante con iOS8 / 9/10.



0

Sono riuscito a utilizzare il server web di PHP su OS X. La copia nella directory temporanea / www non ha funzionato per me. Il server Python SimpleHTTPS si lamentava di voler leggere i tipi MIME, probabilmente un problema di sandboxing.

Ecco un server che utilizza 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

La soluzione @ nacho4d è buona. Voglio cambiarlo un po 'ma non so come cambiarlo nel tuo post. Quindi l'ho messo qui, spero non ti dispiaccia. Grazie.

Nel caso tu abbia una cartella www, ci sono molti altri file come png, css, js ecc. Quindi devi copiare tutti i file nella cartella tmp / www. ad esempio, hai una cartella www come questa: inserisci qui la descrizione dell'immagine

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

la funzione fileURLForBuggyWKWebView8 viene copiata da @ 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

Prova a usare

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Sembra che funzioni ancora. Ancora.


Grazie lo proverò.
Lim Thye Chean

3
Funziona solo per il file HTML stesso, non per le risorse, giusto?
Lim Thye Chean

1
Sfortunatamente sì, sembra che venga caricato solo il file HTML. Speriamo che sia solo un bug e non una nuova restrizione per caricare i file locali. Sto cercando di trovare questo bug nei sorgenti WebKit.
Oleksii

1
Ho riscontrato lo stesso problema: i file HTML vengono caricati ma le immagini e le altre risorse che si trovano sul file system locale non vengono caricate. Nella console, WebKit genera un errore "non è consentito caricare la risorsa locale". Ho segnalato un bug per questo, radar # 17835098
mszaro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.