WKWebView nie ładuje plików lokalnych w iOS 8

123

W przypadku poprzednich wersji beta systemu iOS 8 załaduj lokalną aplikację internetową (w pakiecie) i działa ona dobrze zarówno w przypadku, jak UIWebViewi WKWebView, a nawet przeportowałem grę internetową za pomocą nowego WKWebViewinterfejsu API.

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

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

view.addSubview(webView)

Ale w wersji beta 4 po prostu pojawił się pusty biały ekran ( UIWebViewnadal działa), wygląda na to, że nic nie jest ładowane ani wykonywane. Widziałem błąd w logu:

Nie udało się utworzyć rozszerzenia piaskownicy dla /

Jakaś pomoc, która poprowadzi mnie we właściwym kierunku? Dzięki!

Lim Thye Chean
źródło
Spróbuj również dodać webView do hierarchii widoków w viewDidLoad i załaduj żądanie w viewWillAppear. Mój WKWebView nadal działa, ale tak to mam. Być może WebView ma optymalizację, aby nie ładować żądań, jeśli nie znajdują się one w hierarchii widoków?
rvijay007
Gotowe (view.addView w viewDidLoad i loadRequest w viewWillAppear) i otrzymałem ten sam biały ekran i ten sam komunikat o błędzie.
Lim Thye Chean
9
Wydaje się, że dzieje się tak tylko na urządzeniu, ponieważ na symulatorze działa dobrze. Nawiasem mówiąc, używam Objc.
GuidoMB
1
Pozostaje to problemem w XCode 6 - beta 7. Moim tymczasowym rozwiązaniem było użycie github.com/swisspol/GCDWebServer do obsługi plików lokalnych.
GuidoMB
2
Czy ktoś przetestował to pod iOS 8.0.1?
Lim Thye Chean

Odpowiedzi:

106

W końcu rozwiązali błąd! Teraz możemy użyć -[WKWebView loadFileURL:allowingReadAccessToURL:]. Najwyraźniej poprawka była warta kilka sekund w wideo WWDC 2015 504 Przedstawiamy kontroler Safari View

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

Dla iOS8 ~ iOS10 (Swift 3)

Jak podaje odpowiedź Dana Fabulisha, jest to błąd WKWebView, który najwyraźniej nie zostanie rozwiązany w najbliższym czasie i jak powiedział, istnieje obejście :)

Odpowiadam tylko dlatego, że chciałem pokazać tutaj pracę. Kod IMO pokazany na https://github.com/shazron/WKWebViewFIleUrlTest jest pełen niepowiązanych szczegółów, którymi większość ludzi prawdopodobnie nie jest zainteresowana.

Obejście to 20 linii kodu, obsługa błędów i komentarze, bez serwera :)

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
}

I może być używany jako:

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
źródło
2
Niektóre modyfikacje, aby skopiować cały folder, obrazy i wszystko. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf,
1
Rozwiązanie Piwaf działało dla mnie nieco lepiej, zauważ, że powinno to być „self.webView”, a nie „self.loadingWebView”, jednak biorąc pod uwagę powyższy przykład
Patrick Fabrizius
2
Dzięki @ nacho4d. tldr; rozwiązanie dla folderu / tmp nie będzie działać w wersji 8.0, ale będzie działać w wersji 8.0.2. Miałem problemy z uruchomieniem tego rozwiązania na moim urządzeniu testowym i ostatecznie sklonowałem repozytorium Shazron, aby spróbować. To też nie zadziałało. Okazuje się, że rozwiązanie Shazron nie działa na wersji iOS, na moim urządzeniu działało 8.0 (12A366) na iPhonie 6 Plus. Wypróbowałem to na urządzeniu (iPad Mini) z systemem iOS 8.0.2 i działa dobrze.
jvoll
1
należy pozwolić _ = spróbować? fm.removeItemAtURL (dstURL) zamiast let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Tylko dla prostych stron internetowych. Jeśli używasz Ajaxa lub ładujesz widoki lokalne przez kątowe, spodziewaj się, że „Żądania krzyżowe są obsługiwane tylko dla HTTP”. Jedyną alternatywą jest podejście do lokalnego serwera WWW, którego nie lubię, ponieważ jest widoczne w sieci lokalnej. To wymaga odnotowania w poście, oszczędzając ludziom kilka godzin.
wydarzenie jenson-button
83

WKWebView nie może załadować treści z pliku: adresy URL za pomocą swojej loadRequest:metody. http://www.openradar.me/18039024

Możesz załadować zawartość przez loadHTMLString:, ale jeśli twój baseURL jest plikiem: URL, to nadal nie będzie działać.

iOS 9 ma nowego API, które będą robić to, co chcesz, [WKWebView loadFileURL:allowingReadAccessToURL:] .

Istnieje obejście dla iOS 8 , pokazane przez shazrona w Objective-C tutaj https://github.com/shazron/WKWebViewFIleUrlTest, aby skopiować pliki /tmp/wwwi załadować je stamtąd .

Jeśli pracujesz w Swift, możesz zamiast tego wypróbować sample nachos4d . (Jest również znacznie krótszy niż próbka shazrona, więc jeśli masz problem z kodem shazrona, spróbuj zamiast tego).

Dan Fabulich
źródło
1
Jednym ze sposobów obejścia tego problemu (wspomnianym tutaj: devforums.apple.com/message/1051027 ) jest przeniesienie zawartości do tmp i dostęp do niej stamtąd. Mój szybki test wydaje się wskazywać, że to działa ...
Mike M
6
Nie naprawiono w 8.1.
mat.
1
U mnie działa przenoszenie plików do / tmp. Ale ... mój Boże, jak to przeszło przez testy?
Greg Maletic
1
Ta próbka to ZBYT DUŻO kodu tylko dla wersji demonstracyjnej. Plik do odczytania musi znajdować się wewnątrz /tmp/www/. Użyj NSTemporaryDirectory()i, NSFileManageraby utworzyć wwwkatalog (ponieważ domyślnie nie ma takiego katalogu). Następnie skopiuj tam swój plik i przeczytaj ten plik :)
nacho4d
2
8.3 i nadal nie jest naprawione ...?
ninjaneer
8

Przykład użycia [WKWebView loadFileURL: allowedReadAccessToURL:] na iOS 9 .

Kiedy przenosisz folder sieciowy do projektu, wybierz „Utwórz odniesienia do folderów”

wprowadź opis obrazu tutaj

Następnie użyj kodu podobnego do tego (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)
  }
}

W pliku html użyj takich ścieżek plików

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

nie tak jak to

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

Przykład katalogu, który został przeniesiony do projektu Xcode.

wprowadź opis obrazu tutaj

Markus T.
źródło
6

Tymczasowe obejście: używam GCDWebServer , zgodnie z sugestią GuidoMB.

Najpierw znajduję ścieżkę do mojego folderu „www /” (który zawiera „index.html”):

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

... a następnie uruchom to tak:

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

Aby to zatrzymać:

[_webServer stop];
_webServer = nil;

Wydajność wydaje się dobra, nawet na iPadzie 2.


Zauważyłem awarię po tym, jak aplikacja przeszła w tło, więc zatrzymuję ją applicationDidEnterBackground:i applicationWillTerminate:; Uruchamiam / restartuję go na application:didFinishLaunching...i applicationWillEnterForeground:.

EthanB
źródło
1
Korzystanie z „serwer” prawdopodobnie pochłaniają żadnych korzyści wydajności, które WKWebViewzawiera ponad UIWebView. Równie dobrze można trzymać się starego interfejsu API, dopóki nie zostanie to naprawione.
ray
@ray Nie z aplikacją jednostronicową.
EthanB,
Mam GCDWebServer do pracy również w wewnętrznych kompilacjach mojej aplikacji. A jeśli w Twojej aplikacji jest dużo kodu JavaScript, serwer jest tego wart. Ale są inne problemy, które uniemożliwiają mi teraz korzystanie z WKWebView, więc mam nadzieję na ulepszenia w iOS 9.
Tom Hamming
A jak pokazać to w WebView? Naprawdę nie rozumiem, do czego służy GCDWebServer?
chipbk10
1
Zamiast kierować widok sieciowy na „file: // .....”, kieruj go na „http: // localhost: <port> / ...”.
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

To rozwiązało problem dla mnie iOS 8.0+ dev.apple.com

to też wydaje się działać dobrze ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
źródło
zamiast FILE możesz też wstawić DIR.
nullqube
To świetne znalezisko. Nie wiem, dlaczego nie głosowano wyżej.
plivesey
Ten post zawiera więcej informacji (w tym linki do źródła webkit): stackoverflow.com/questions/36013645/ ...
plivesey
configuration.preferences setValue spowoduje awarię na iOS 9.3
Vignesh Kumar
Używam zestawu SDK systemu iOS 13, ale staram się zachować zgodność z systemem iOS 8, a allowFileAccessFromFileURLspodejście ulega awarii z NSUnknownKeyException.
arlomedia
4

Oprócz rozwiązań wspomnianych przez Dana Fabulicha, XWebView to kolejne obejście. [WKWebView loadFileURL: allowReadAccessToURL:] jest implementowane poprzez rozszerzenie .

blask
źródło
1
Zdecydowałem się użyć XWebView po przyjrzeniu się innym obejściom tego problemu. XWebView to framework zaimplementowany w Swift, ale nie miałem problemu z używaniem go w mojej aplikacji Objective-C na iOS 8.
chmaynard
4

Nie mogę jeszcze komentować, więc zamieszczam to jako osobną odpowiedź.

To jest obiektywna wersja rozwiązania nacho4d . Najlepsze obejście, jakie do tej pory widziałem.

- (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
źródło
1
Nie mogę zmusić tego do działania na iOS 8 w symulatorze. Chcę umieścić plik .png w / tmp / www, a następnie użyć tagu img w moim html. Czego należy użyć dla src tagu img?
user3246173
był dokładnie tym, czego potrzebowałem. Dzięki!
Tinkerbell
3

W przypadku, gdy próbujesz wyświetlić lokalny obraz w środku większego ciągu HTML, takiego jak <img src="file://...">:, nadal nie pojawia się on na urządzeniu, więc załadowałem plik obrazu do NSData i mogłem go wyświetlić, zastępując ciąg src ciągiem same dane. Przykładowy kod ułatwiający tworzenie ciągu znaków HTML do załadowania do WKWebView, gdzie wynik zastąpi to, co znajduje się w cudzysłowie src = "":

Szybki:

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

Cel 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
źródło
w wersji szybkiej dodaj średnik przed base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil
Dzięki, to jedyna rzecz, która działała dla mnie, ponieważ pobieram plik .gif do folderu tymczasowego na urządzeniu, a następnie ładuję ten plik WKWebViewjako plik <img>HTMLstring.
Pan Zystem
1

Używam poniżej. Ma kilka dodatkowych rzeczy, nad którymi pracuję, ale możesz zobaczyć, gdzie skomentowałem loadRequest i zastępuję wywołanie loadHTMLString. Mam nadzieję, że to pomoże, dopóki nie naprawią błędu.

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
źródło
3
Wydaje się, że działa to tylko dla plików HTML, ale nie dla innych zasobów.
Lim Thye Chean
1

Kto musi obejść ten problem w systemie iOS8:

Jeśli Twoja strona nie jest skomplikowana, możesz zdecydować się na utworzenie jej jako aplikacji jednostronicowej.

Innymi słowy, aby osadzić wszystkie zasoby w pliku html.

Aby to zrobić: 1. skopiuj zawartość swojego pliku js / css odpowiednio do / tags w pliku html; 2. przekonwertować pliki graficzne do formatu svg, aby zastąpić odpowiednio. 3. załaduj stronę jak poprzednio, używając na przykład [webView loadHTMLString: baseURL:]

To było trochę inne niż stylizowanie obrazu svg, ale nie powinno Cię tak bardzo blokować.

Wydawało się, że wydajność renderowania strony nieco spadła, ale warto było mieć tak proste obejście działające pod iOS8 / 9/10.

firebear
źródło
0

Udało mi się użyć serwera WWW PHP na OS X. Kopiowanie do katalogu tymczasowego / www nie działało. Python SimpleHTTPServer narzekał, że chce czytać typy MIME, prawdopodobnie z powodu piaskownicy.

Oto serwer używający 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
źródło
0

@ Rozwiązanie nacho4d jest dobre. Chcę to trochę zmienić, ale nie wiem, jak to zmienić w Twoim poście. Więc umieściłem to tutaj, mam nadzieję, że nie masz nic przeciwko. dzięki.

W przypadku, gdy masz folder www, istnieje wiele innych plików, takich jak png, css, js itp. Następnie musisz skopiować wszystkie pliki do folderu tmp / www. na przykład masz taki folder www: wprowadź opis obrazu tutaj

następnie w 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)")
        }
    }
}

funkcja fileURLForBuggyWKWebView8 jest kopiowana z @ 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
źródło
-1

Spróbuj użyć

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Wygląda na to, że nadal działa. Jeszcze.

Oleksii
źródło
Dzięki, spróbuję.
Lim Thye Chean,
3
Działa to tylko dla samego pliku HTML, a nie zasobów, prawda?
Lim Thye Chean
1
Niestety tak, wydaje się, że ładuje się tylko plik HTML. Miejmy nadzieję, że to tylko błąd, a nie nowe ograniczenie do ładowania plików lokalnych. Próbuję znaleźć ten błąd w źródłach WebKit.
Oleksii
1
Napotkałem ten sam problem - pliki HTML są ładowane, ale obrazy i inne zasoby znajdujące się w lokalnym systemie plików nie są ładowane. W konsoli WebKit generuje błąd „nie można załadować lokalnego zasobu”. Zgłosiłem błąd w tym, radar # 17835098
mszaro