O WKWebView não carrega arquivos locais no iOS 8

123

Para anteriores iOS 8 betas, carregar um aplicativo web local (em Bundle) e funciona bem para ambos UIWebViewe WKWebView, e eu até portado um jogo web usando a nova WKWebViewAPI.

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

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

view.addSubview(webView)

Mas na versão beta 4, recebi uma tela branca em branco ( UIWebViewainda funciona), parece que nada foi carregado ou executado. Eu vi um erro no log:

Não foi possível criar uma extensão de sandbox para /

Alguma ajuda para me guiar na direção certa? Obrigado!

Lim Thye Chean
fonte
Tente também adicionar o webView à hierarquia de exibição em viewDidLoad e carregue a solicitação em viewWillAppear. Meu WKWebView ainda está funcionando, mas é assim que eu o tenho. Talvez o WebView tenha uma otimização para não carregar solicitações se elas não estiverem em uma hierarquia de exibição?
rvijay007
Concluído (o view.addView em viewDidLoad e loadRequest no viewWillAppear), e recebi a mesma tela branca e a mesma mensagem de erro.
Lim Thye Chean
9
Isso parece acontecer apenas no dispositivo, porque no simulador funciona bem. Estou usando Objc a propósito.
GuidoMB
1
Isso ainda é um problema no XCode 6 - beta 7. Minha solução temporária foi usar github.com/swisspol/GCDWebServer para servir arquivos locais.
GuidoMB
2
Alguém testou no iOS 8.0.1?
Lim Thye Chean

Respostas:

106

Eles finalmente resolveram o bug! Agora podemos usar -[WKWebView loadFileURL:allowingReadAccessToURL:]. Aparentemente, a correção valeu alguns segundos no vídeo 504 da WWDC 2015.

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

Para iOS8 ~ iOS10 (Swift 3)

Como a resposta de Dan Fabulish afirma, este é um bug do WKWebView que aparentemente não está sendo resolvido tão cedo e, como ele disse, há uma solução alternativa :)

Estou respondendo apenas porque queria mostrar a solução aqui. O código IMO mostrado em https://github.com/shazron/WKWebViewFIleUrlTest está cheio de detalhes não relacionados nos quais a maioria das pessoas provavelmente não está interessada.

A solução alternativa é 20 linhas de código, tratamento de erros e comentários incluídos, sem a necessidade de um servidor :)

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 pode ser usado como:

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
fonte
2
Algumas modificações para copiar a pasta inteira, imagens e tudo. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf
1
A solução da Piwaf trabalhado um pouco melhor para mim, nota que ele deve ser "self.webView" e não "self.loadingWebView" dado o exemplo acima embora
Patrick Fabrizius
2
Obrigado @ nacho4d. tldr; a solução da pasta / tmp não funcionará no 8.0, mas no 8.0.2. Eu estava com problemas para fazer com que esta solução funcionasse no meu dispositivo de teste e, eventualmente, clonou o repositório do shazron para experimentá-lo. Isso também não funcionou. Acontece que a solução da shazron não funciona na versão do iOS, meu dispositivo estava executando 8.0 (12A366) no iPhone 6 Plus. Eu tentei em um dispositivo (iPad Mini) com iOS 8.0.2 e funciona bem.
jvoll
1
deve ser deixado _ = tentar? fm.removeItemAtURL (dstURL) em vez de deixar _ = tentar? fileMgr.removeItemAtURL (dstURL)
DáChún
3
Somente para sites simples. Se você estiver usando ajax ou carregando visualizações locais via angular, espere "Solicitações de origem cruzada são suportadas apenas para HTTP". Sua única alternativa é a abordagem de servidor da web local da qual não gosto, pois é visível na rede local. Isso precisa anotar no post, poupar as pessoas algumas horas.
Jenson-button-event
83

O WKWebView não pode carregar o conteúdo do arquivo: URLs através de seu loadRequest:método. http://www.openradar.me/18039024

Você pode carregar o conteúdo via loadHTMLString:, mas se a sua baseURL for um arquivo: URL, ele ainda não funcionará.

O iOS 9 possui uma nova API que fará o que você deseja [WKWebView loadFileURL:allowingReadAccessToURL:] ,.

Existe uma solução alternativa para o iOS 8 , demonstrada pelo shazron no Objective-C aqui https://github.com/shazron/WKWebViewFIleUrlTest para copiar arquivos /tmp/wwwe carregá-los a partir daí .

Se você estiver trabalhando no Swift, tente a amostra do nachos4d . (Também é muito mais curto que a amostra do shazron, por isso, se você estiver tendo problemas com o código do shazron, experimente.)

Dan Fabulich
fonte
1
Uma solução alternativa (mencionada aqui: devforums.apple.com/message/1051027 ) é mover o conteúdo para tmp e acessá-lo a partir daí. Meu teste rápido parece indicar que que ela não funciona ...
Mike M
6
Não corrigido no 8.1.
Matt
1
Mover os arquivos para / tmp funciona para mim. Mas ... meu Deus, como isso passou nos testes?
Greg Maletic 15/01/15
1
Esse exemplo é MUITO MUITO código apenas para uma demonstração. O arquivo a ser lido deve estar dentro /tmp/www/. Use NSTemporaryDirectory()e NSFileManagerpara criar o wwwdiretório (porque não existe esse diretório por padrão). Em seguida, copie o arquivo lá e agora ler este arquivo :)
nacho4d
2
8.3 e ainda não está consertado ...?
Ninjaneer
8

Um exemplo de como usar [WKWebView loadFileURL: allowReadAccessToURL:] no iOS 9 .

Ao mover a pasta da web para um projeto, selecione "Criar referências de pasta"

insira a descrição da imagem aqui

Em seguida, use um código parecido com este (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)
  }
}

No arquivo html, use caminhos de arquivo como este

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

Assim não

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

Um exemplo de diretório que é movido para um projeto xcode.

insira a descrição da imagem aqui

Markus T.
fonte
6

Solução temporária: estou usando o GCDWebServer , conforme sugerido pelo GuidoMB.

Encontrei o caminho da minha pasta "www /" empacotada (que contém um "index.html"):

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

... então inicie-o assim:

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

Para pará-lo:

[_webServer stop];
_webServer = nil;

O desempenho parece bom, mesmo em um iPad 2.


Eu notei uma falha depois que o aplicativo entra em segundo plano, então eu o interrompo applicationDidEnterBackground:e applicationWillTerminate:; Eu inicio / reinicio application:didFinishLaunching...e applicationWillEnterForeground:.

EthanB
fonte
1
O uso de um "servidor" provavelmente consome todos os benefícios de desempenho que WKWebViewoferecem mais UIWebView. É melhor ficar com a API antiga até que isso seja corrigido.
ray
@ray Não com um aplicativo de página única.
EthanB
Consegui que o GCDWebServer funcionasse também, nas versões internas do meu aplicativo. E se houver muito javascript no seu aplicativo, o servidor valerá totalmente a pena. Mas há outras questões que me impedem de usar WKWebView agora, então eu estou esperando por melhorias no iOS 9.
Tom Hamming
E como mostrá-lo em um WebView? Realmente não entendo para que serve o GCDWebServer?
chipbk10
1
Em vez de apontar a visualização da web para "file: // .....", aponte para "http: // localhost: <port> / ...".
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Isso resolveu o problema para mim iOS 8.0+ dev.apple.com

também isso parece funcionar muito bem também ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
fonte
em vez de FILE, você também pode colocar DIR.
Nulo6 6/08
Esta é uma ótima descoberta. Não sei por que não é votado mais alto.
plivesey
Esta postagem tem mais informações (incluindo links para a fonte do
kit da web
O setValue da configuração.preferences causará uma falha no iOS 9.3
Vignesh Kumar
Estou usando o iOS 13 SDK, mas estou tentando manter a compatibilidade com o iOS 8, e a allowFileAccessFromFileURLsabordagem falha NSUnknownKeyException.
arlomedia 28/04
4

Além das soluções mencionadas por Dan Fabulich, o XWebView é outra solução alternativa. [WKWebView loadFileURL: allowReadAccessToURL:] é implementado através da extensão .

soflare
fonte
1
Decidi usar o XWebView depois de examinar outras soluções alternativas para esse problema. O XWebView é uma estrutura implementada no Swift, mas não tive problemas em usá-lo no meu aplicativo iOS 8 Objective-C.
Chmaynard 30/05
4

Ainda não posso comentar, por isso estou postando isso como uma resposta separada.

Esta é uma versão objetiva-c da solução do nacho4d . A melhor solução alternativa que eu já vi até agora.

- (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
fonte
1
Não consigo fazer isso funcionar no iOS 8 no simulador. Quero colocar um .png em / tmp / www e depois usar a tag img no meu html. O que devo usar para a tag img src?
user3246173
era exatamente o que eu precisava. Obrigado!
Tinkerbell
3

No caso de você estar tentando exibir uma imagem local no meio de uma string HTML maior como <img src="file://...">:, ela ainda não aparece no dispositivo, então eu carreguei o arquivo de imagem no NSData e pude exibi-la substituindo a string src por os dados em si. Código de amostra para ajudar a criar a string HTML a ser carregada no WKWebView, onde resultado é o que substituirá o que está dentro das aspas de src = "":

Rápido:

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

Objetivo-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
fonte
na versão rápida, adicione um ponto-e-vírgula antes da base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil 28/02/19
Obrigado, essa é a única coisa que funcionou para mim, porque faço o download de um arquivo .gif para uma pasta temporária no dispositivo e carrego esse arquivo no WKWebViewcomo <img>dentro de uma string HTML.
Sr. Zystem
1

Estou usando o abaixo. Tenho algumas coisas extras em que estou trabalhando, mas você pode ver onde comentei o loadRequest e estou substituindo a chamada loadHTMLString. Espero que isso ajude até que eles consertem o 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)")

    }

}
Dustin Nielson
fonte
3
Isso parece funcionar apenas para os arquivos HTML, mas não para outros recursos.
Lim Thye Chean
1

Para quem deve solucionar esse problema no iOS8:

Se a sua página não for complicada, você pode optar por torná-la como um Aplicativo de Página Única.

Em outras palavras, para incorporar todos os recursos no arquivo html.

Para fazer: 1. copie o conteúdo do seu arquivo js / css para / tags no arquivo html, respectivamente; 2. converta seus arquivos de imagem em svg para substituir de acordo. 3. carregue a página como antes, usando [webView loadHTMLString: baseURL:], por exemplo

Era um pouco diferente de estilizar uma imagem svg, mas não deveria te bloquear tanto.

Parecia que o desempenho de renderização da página diminuía um pouco, mas vale a pena ter uma solução alternativa tão simples funcionando no iOS8 / 9/10.

urso de fogo
fonte
0

Eu consegui usar o servidor da web do PHP no OS X. Copiar para o diretório / www temporário não funcionou para mim. O Python SimpleHTTPServer se queixou de querer ler tipos MIME, provavelmente um problema de sandbox.

Aqui está um servidor usando 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
fonte
0

@ Solução nacho4d é boa. Quero mudar um pouco, mas não sei como alterá-lo em sua postagem. Então, eu coloquei aqui, espero que você não se importe. obrigado.

Caso você tenha uma pasta www, existem muitos outros arquivos, como png, css, js, etc. Em seguida, você deve copiar todos os arquivos para a pasta tmp / www. por exemplo, você tem uma pasta www como esta: insira a descrição da imagem aqui

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

a função fileURLForBuggyWKWebView8 é copiada 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
}
DàChún
fonte
-1

Tente usar

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Parece que ainda está funcionando. Ainda.

Oleksii
fonte
Obrigado, vou experimentar.
Lim Thye Chean
3
Isso funciona apenas para o arquivo HTML em si, não para os recursos, certo?
Lim Thye Chean
1
Infelizmente sim, apenas o arquivo HTML parece estar carregando. Vamos torcer para que seja apenas um bug, e não uma nova restrição para carregar arquivos locais. Estou tentando encontrar esse bug nas fontes do WebKit.
27614 Oleksii
1
Corri para o mesmo problema - os arquivos HTML são carregados, mas as imagens e outros recursos que estão no sistema de arquivos local não são carregados. No console, o WebKit está lançando um erro "não é permitido carregar o recurso local". Eu arquivei um bug para isso, radar # 17835098
mszaro