WKWebView no carga archivos locales en iOS 8

123

Para iOS 8 anteriores betas, cargar una aplicación web local (en paquete) y funciona bien para ambos UIWebViewy WKWebView, e incluso he portado un juego web usando la nueva WKWebViewAPI.

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

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

view.addSubview(webView)

Pero en la versión beta 4, acabo de recibir una pantalla en blanco en blanco ( UIWebViewtodavía funciona), parece que no hay nada cargado o ejecutado. Vi un error en el registro:

No se pudo crear una extensión de sandbox para /

¿Alguna ayuda para guiarme en la dirección correcta? ¡Gracias!

Lim Thye Chean
fuente
Intente también agregar webView a la jerarquía de vistas en viewDidLoad y cargue la solicitud en viewWillAppear. Mi WKWebView sigue funcionando, pero así es como lo tengo. ¿Quizás WebView tiene una optimización para no cargar solicitudes si no están en una jerarquía de vistas?
rvijay007
Hecho (view.addView en viewDidLoad y loadRequest en viewWillAppear), obtuve la misma pantalla en blanco y el mismo mensaje de error.
Lim Thye Chean
9
Esto solo parece suceder en el dispositivo porque en el simulador funciona bien. Estoy usando Objc por cierto.
GuidoMB
1
Esto sigue siendo un problema en XCode 6 - beta 7. Mi solución temporal fue usar github.com/swisspol/GCDWebServer para servir archivos locales.
GuidoMB
2
¿Alguien lo probó con iOS 8.0.1?
Lim Thye Chean

Respuestas:

106

¡Finalmente resolvieron el error! Ahora podemos usar -[WKWebView loadFileURL:allowingReadAccessToURL:]. Aparentemente, la solución valió unos segundos en el video 504 de WWDC 2015 que presenta Safari View Controller

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

Para iOS8 ~ iOS10 (Swift 3)

Como dice la respuesta de Dan Fabulish, este es un error de WKWebView que aparentemente no se está resolviendo en el corto plazo y, como dijo, hay una solución alternativa :)

Estoy respondiendo solo porque quería mostrar el trabajo aquí. El código IMO que se muestra en https://github.com/shazron/WKWebViewFIleUrlTest está lleno de detalles no relacionados que a la mayoría de las personas probablemente no les interese.

La solución es de 20 líneas de código, manejo de errores y comentarios incluidos, sin necesidad de un 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
}

Y se puede usar 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
fuente
2
Algunas modificaciones para copiar toda la carpeta, imágenes y todo. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf
1
La solución de Piwaf funcionó un poco mejor para mí, tenga en cuenta que debería ser "self.webView" y no "self.loadingWebView" dado el ejemplo anterior, sin embargo
Patrick Fabrizius
2
Gracias @ nacho4d. tldr; la solución de carpeta / tmp no funcionará en 8.0 pero sí en 8.0.2. Estaba teniendo problemas para hacer que esta solución funcionara en mi dispositivo de prueba y eventualmente cloné el repositorio de Shazron para probarlo. Eso tampoco funcionó. Resulta que la solución de Shazron no funciona en la versión de iOS, mi dispositivo ejecutaba 8.0 (12A366) en iPhone 6 Plus. Lo probé en un dispositivo (iPad Mini) con iOS 8.0.2 y funciona bien.
jvoll
1
se debe dejar _ = probar? fm.removeItemAtURL (dstURL) en lugar de let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Solo para sitios web simples. Si está utilizando ajax o cargando vistas locales a través de angular, espere "Las solicitudes de origen cruzado solo son compatibles con HTTP". Su único inconveniente es el enfoque del servidor web local que no me gusta ya que es visible en la red local. Esto debe tenerse en cuenta en la publicación, ahorre gente algunas horas.
jenson-button-event
83

WKWebView no puede cargar contenido del archivo: URL a través de su loadRequest:método. http://www.openradar.me/18039024

Puede cargar contenido a través de loadHTMLString:, pero si su baseURL es un archivo: URL, entonces todavía no funcionará.

iOS 9 tiene una nueva API que hará lo que quieras [WKWebView loadFileURL:allowingReadAccessToURL:] ,.

Hay una solución para iOS 8 , demostrada por shazron en Objective-C aquí https://github.com/shazron/WKWebViewFIleUrlTest para copiar archivos /tmp/wwwy cargarlos desde allí .

Si está trabajando en Swift, puede probar la muestra de nachos4d en su lugar. (También es mucho más corto que la muestra de shazron, por lo que si tiene problemas con el código de shazron, inténtelo).

Dan Fabulich
fuente
1
Una solución alternativa (mencionada aquí: devforums.apple.com/message/1051027 ) es mover contenido a tmp y acceder a él desde allí. Mi prueba rápida parece indicar que funciona ...
Mike M
66
No arreglado en 8.1.
mate
1
Mover los archivos a / tmp me funciona. Pero ... Dios mío, ¿cómo pasó esto a través de las pruebas?
Greg Maletic
1
Esa muestra es el código WAY TOO MUCHO solo para una demostración. El archivo a leer debe estar dentro /tmp/www/. Use NSTemporaryDirectory()y NSFileManagerpara crear el wwwdirectorio (porque no existe dicho directorio por defecto). Luego copie su archivo allí y ahora lea este archivo :)
nacho4d
2
8.3 y todavía no está arreglado ...?
ninjaneer
8

Un ejemplo de cómo usar [WKWebView loadFileURL: allowReadAccessToURL:] en iOS 9 .

Cuando mueva la carpeta web a un proyecto, seleccione "Crear referencias de carpeta"

ingrese la descripción de la imagen aquí

Luego use un código similar a 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)
  }
}

En el archivo html, use rutas de archivo como esta

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

así no

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

Un ejemplo de directorio que se mueve a un proyecto xcode.

ingrese la descripción de la imagen aquí

Markus T.
fuente
6

Solución temporal: estoy usando GCDWebServer , como lo sugiere GuidoMB.

Primero encuentro la ruta de mi carpeta "www /" incluida (que contiene un "index.html"):

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

... luego comience así:

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

Para detenerlo:

[_webServer stop];
_webServer = nil;

El rendimiento parece estar bien, incluso en un iPad 2.


Noté un bloqueo después de que la aplicación pasa a un segundo plano, así que la detuve applicationDidEnterBackground:y applicationWillTerminate:; Lo inicio / reinicio application:didFinishLaunching...y applicationWillEnterForeground:.

EthanB
fuente
1
El uso de un "servidor" probablemente consume cualquier beneficio de rendimiento que WKWebViewproporciona UIWebView. También podría seguir con la antigua API hasta que se solucione.
Ray
@ray No con una aplicación de una sola página.
EthanB
También he conseguido que GCDWebServer funcione, en las compilaciones internas de mi aplicación. Y si hay mucho javascript en su aplicación, el servidor lo vale totalmente. Pero hay otros problemas que me impiden usar WKWebView en este momento, así que espero mejoras en iOS 9.
Tom Hamming
¿Y cómo mostrarlo en un WebView? ¿Realmente no entiendo para qué sirve GCDWebServer?
chipbk10
1
En lugar de apuntar la vista web a "archivo: // .....", apunte a "http: // localhost: <port> / ...".
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Esto resolvió el problema para mí iOS 8.0+ dev.apple.com

También esto parece funcionar bien también ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
fuente
en lugar de ARCHIVO puedes poner DIR también.
nullqube
Este es un gran hallazgo. No sé por qué no se vota más alto.
plivesey
Esta publicación tiene más información (incluidos enlaces a la fuente del
kit web
configuration.preferences setValue se bloqueará en iOS 9.3
Vignesh Kumar
Estoy usando el SDK de iOS 13 pero intento mantener la compatibilidad con iOS 8, y el allowFileAccessFromFileURLsenfoque falla NSUnknownKeyException.
arlomedia
4

Además de las soluciones mencionadas por Dan Fabulich, XWebView es otra solución alternativa. [WKWebView loadFileURL: allowReadAccessToURL:] se implementa a través de la extensión .

soflare
fuente
1
Decidí usar XWebView después de buscar otras soluciones para este problema. XWebView es un marco implementado en Swift pero no tuve problemas para usarlo en mi aplicación iOS 8 Objective-C.
chmaynard
4

No puedo comentar todavía, así que estoy publicando esto como una respuesta separada.

Esta es una versión objetiva-c de la solución de nacho4d . La mejor solución que he visto hasta ahora.

- (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
fuente
1
No puedo hacer que esto funcione en iOS 8 en el simulador. Quiero poner un .png en / tmp / www y luego usar la etiqueta img en mi html. ¿Qué debo usar para la etiqueta img src?
user3246173
Era exactamente lo que necesitaba. ¡Gracias!
Tinkerbell
3

En el caso de que intente mostrar una imagen local en el medio de una cadena HTML más grande como: <img src="file://..."> todavía no aparece en el dispositivo, así que cargué el archivo de imagen en NSData y pude mostrarlo reemplazando la cadena src con Los datos en sí. Código de muestra para ayudar a construir la cadena HTML para cargar en WKWebView, donde el resultado es lo que reemplazará lo que está dentro de las comillas 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

C objetivo:

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
fuente
en la versión rápida, agregue un punto y coma antes de base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil
Gracias, esto es lo único que funcionó para mí, porque descargué un archivo .gif a una carpeta temporal en el dispositivo y luego cargué ese archivo WKWebViewcomo <img>dentro de una cadena HTML.
Sr. Zystem
1

Estoy usando el siguiente. Tengo algunas cosas adicionales en las que estoy trabajando, pero puedes ver dónde he comentado la loadRequest y estoy sustituyendo la llamada loadHTMLString. Espero que esto ayude hasta que arreglen el error.

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
fuente
3
Esto parece funcionar solo para los archivos HTML pero no para otros recursos.
Lim Thye Chean
1

Para quién debe solucionar este problema en iOS8:

Si su página no es complicada, puede optar por hacer la página como una aplicación de página única.

En otras palabras, para incrustar todos los recursos en el archivo html.

Para hacer: 1. copie el contenido de su archivo js / css en / tags en el archivo html respectivamente; 2. Convierta sus archivos de imagen en svg para reemplazarlos en consecuencia. 3. cargue la página como antes, usando [webView loadHTMLString: baseURL:], por ejemplo

Era un poco diferente al estilo de una imagen svg, pero no debería bloquearlo tanto.

Parecía que el rendimiento del renderizado de la página disminuyó un poco, pero valió la pena tener una solución tan simple que funcione con iOS8 / 9/10.

oso de fuego
fuente
0

Me las arreglé para usar el servidor web de PHP en OS X. Copiar al directorio temporal / www no funcionó para mí. Python SimpleHTTPServer se quejó de querer leer tipos MIME, probablemente un problema de sandboxing.

Aquí hay un servidor que usa 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
fuente
0

La solución @ nacho4d es buena. Quiero cambiarlo un poco, pero no sé cómo cambiarlo en tu publicación. Así que lo puse aquí, espero que no te importe. Gracias.

En caso de que tenga una carpeta www, hay muchos otros archivos como png, css, js, etc. Luego debe copiar todos los archivos a la carpeta tmp / www. por ejemplo, tiene una carpeta www como esta: ingrese la descripción de la imagen aquí

luego en 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 función fileURLForBuggyWKWebView8 se copia 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
fuente
-1

Intenta usar

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Parece que todavía está funcionando. Todavía.

Oleksii
fuente
Gracias lo intentaré.
Lim Thye Chean
3
Esto solo funciona para el archivo HTML en sí, no para los recursos, ¿verdad?
Lim Thye Chean
1
Desafortunadamente sí, solo el archivo HTML parece estar cargando. Esperemos que sea solo un error y no una nueva restricción para cargar archivos locales. Estoy tratando de encontrar este error en las fuentes de WebKit.
Oleksii
1
Me he encontrado con el mismo problema: los archivos HTML se cargan pero las imágenes y otros recursos que están en el sistema de archivos local no se cargan. En la consola, WebKit arroja un error "no se permite cargar recursos locales". Archivé un error para esto, radar # 17835098
mszaro