WKWebView не загружает локальные файлы под iOS 8

123

Для предыдущих IOS 8 бет, загрузить локальный веб - приложение (в комплекте) , и он прекрасно работает как UIWebViewи WKWebView, и я даже портировали веб - игры с помощью нового WKWebViewAPI.

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

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

view.addSubview(webView)

Но в бета-версии 4 у меня только что был пустой белый экран ( UIWebViewвсе еще работает), похоже, ничего не загружается и не выполняется. Увидел в журнале ошибку:

Не удалось создать расширение песочницы для /

Любая помощь, чтобы направить меня в правильном направлении? Спасибо!

Лим Тай Чин
источник
Также попробуйте добавить webView в иерархию представлений в viewDidLoad и загрузить запрос в viewWillAppear. Мой WKWebView все еще работает, но так оно и есть. Возможно, у WebView есть оптимизация, чтобы не загружать запросы, если они не находятся в иерархии представлений?
rvijay007
Готово (view.addView в viewDidLoad и loadRequest в viewWillAppear), и я получил тот же белый экран и такое же сообщение об ошибке.
Лим Тай Чин
9
Это происходит только на устройстве, потому что на симуляторе все работает нормально. Кстати, я использую Objc.
GuidoMB 02
1
Это остается проблемой в XCode 6 - beta 7. Мое временное решение заключалось в использовании github.com/swisspol/GCDWebServer для обслуживания локальных файлов.
GuidoMB 05
2
Кто-нибудь тестировал его под iOS 8.0.1?
Лим Тай Чин

Ответы:

106

Наконец-то они решили ошибку! Теперь мы можем использовать -[WKWebView loadFileURL:allowingReadAccessToURL:]. По-видимому, исправление стоило нескольких секунд в видеоролике WWDC 2015 504 Введение в Safari View Controller

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

Для iOS8 ~ iOS10 (Swift 3)

В ответе Дэна Фабулиша говорится, что это ошибка WKWebView, которая, по-видимому, не будет решена в ближайшее время, и, как он сказал, есть обходной путь :)

Я отвечаю только потому, что хотел показать здесь обходной путь. Код IMO, показанный в https://github.com/shazron/WKWebViewFIleUrlTest , полон несвязанных деталей, которые, вероятно, не интересуют большинство людей.

Обход - 20 строк кода, обработка ошибок и комментарии включены, сервер не нужен :)

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
}

И может использоваться как:

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
источник
2
Некоторые модификации для того, чтобы скопировать всю папку, изображения и все такое. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf 09
1
Решение Piwaf сработало для меня немного лучше, обратите внимание, что это должно быть «self.webView», а не «self.loadingWebView», учитывая приведенный выше пример
Патрик Фабрициус
2
Спасибо @ nacho4d. tldr; решение для папки / tmp не будет работать в версии 8.0, но будет работать в версии 8.0.2. У меня возникли проблемы с тем, чтобы это решение работало на моем тестовом устройстве, и в конечном итоге я клонировал репозиторий Shazron, чтобы попробовать. Это тоже не сработало. Оказывается, решение Shazron не работает с версией iOS, на которой на моем устройстве была установлена ​​версия 8.0 (12A366) на iPhone 6 Plus. Я попробовал его на устройстве (iPad Mini) под управлением iOS 8.0.2, и он отлично работает.
jvoll
1
это надо дать _ = попробовать? fm.removeItemAtURL (dstURL) вместо let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Только для простых сайтов. Если вы используете ajax или загружаете локальные представления через angular, ожидайте, что «запросы перекрестного происхождения поддерживаются только для HTTP». Ваш единственный запасной вариант - это подход локального веб-сервера, который мне не нравится, поскольку он виден в локальной сети. Это нужно отметить в посте, сэкономьте несколько часов.
jenson-button-event
83

WKWebView не может загружать контент из URL-адресов file: через свой loadRequest:метод. http://www.openradar.me/18039024

Вы можете загружать контент через loadHTMLString:, но если ваш baseURL - это URL-адрес file:, он все равно не будет работать.

IOS 9 имеет новый API , который будет делать то , что вы хотите, [WKWebView loadFileURL:allowingReadAccessToURL:] .

Существует обходной путь для iOS 8 , продемонстрированный shazron в Objective-C здесь https://github.com/shazron/WKWebViewFIleUrlTest, чтобы копировать файлы /tmp/wwwи загружать их оттуда .

Если вы работаете в Swift, вы можете вместо этого попробовать образец nachos4d . (Он также намного короче, чем образец Shazron, поэтому, если у вас возникли проблемы с кодом Shazron, попробуйте вместо этого.)

Дэн Фабулич
источник
1
Один из обходных путей (упомянутый здесь: devforums.apple.com/message/1051027 ) - переместить контент в tmp и получить к нему доступ оттуда. Мой быстрый тест, кажется, показывает, что он действительно работает ...
Майк М.
6
Не исправлено в 8.1.
Мэтт
1
У меня работает перемещение файлов в / tmp. Но ... Боже мой, как это прошло тестирование?
Грег Малетич
1
Этот образец - СЛИШКОМ МНОГО кода только для демонстрации. Читаемый файл должен находиться внутри /tmp/www/. Используйте NSTemporaryDirectory()и NSFileManagerдля создания wwwкаталога (потому что по умолчанию такого каталога нет). Затем скопируйте туда свой файл и теперь прочтите этот файл :)
nacho4d
2
8.3 и до сих пор не исправлено ...?
ninjaneer 09
8

Пример использования [WKWebView loadFileURL: AllowReadAccessToURL:] в iOS 9 .

При перемещении веб-папки в проект выберите «Создать ссылки на папки»

введите описание изображения здесь

Затем используйте примерно такой код (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)
  }
}

В файле html используйте такие пути к файлам

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

не так

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

Пример каталога, перемещенного в проект xcode.

введите описание изображения здесь

Маркус Т.
источник
6

Временное решение: я использую GCDWebServer , как предлагает GuidoMB.

Сначала я нахожу путь к моей связанной папке «www /» (которая содержит «index.html»):

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

... затем запустите его так:

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

Чтобы остановить это:

[_webServer stop];
_webServer = nil;

Производительность выглядит нормально даже на iPad 2.


Я заметил сбой после того, как приложение перешло в фоновый режим, поэтому я остановил его applicationDidEnterBackground:и applicationWillTerminate:; Запускаю / перезапускаю на application:didFinishLaunching...и applicationWillEnterForeground:.

EthanB
источник
1
Использование «сервера», вероятно, съедает любые преимущества в производительности, которые он WKWebViewдает UIWebView. Можно также придерживаться старого API, пока это не будет исправлено.
Ray
@ray Не с одностраничным приложением.
EthanB
У меня GCDWebServer тоже работает во внутренних сборках моего приложения. И если в вашем приложении много javascript, сервер того стоит. Но есть и другие проблемы, которые мешают мне использовать WKWebView прямо сейчас, поэтому я надеюсь на улучшения в iOS 9.
Том Хэмминг,
И как это показать в WebView? Я действительно не понимаю, для чего нужен GCDWebServer?
chipbk10
1
Вместо того, чтобы указывать веб-просмотр на «file: // .....», вы указываете его на «http: // localhost: <port> / ...».
EthanB,
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Это решило проблему для меня iOS 8.0+ dev.apple.com

также, похоже, это тоже сработало отлично ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
источник
вместо ФАЙЛА тоже можно поставить DIR.
nullqube
Это отличная находка. Не знаю, почему за него не проголосовали выше.
plivesey
В этом посте есть дополнительная информация (включая ссылки на источник webkit): stackoverflow.com/questions/36013645/…
plivesey
configuration.preferences setValue вызовет сбой на iOS 9.3
Виньеш Кумар,
Я использую SDK для iOS 13, но пытаюсь сохранить совместимость с iOS 8, и этот allowFileAccessFromFileURLsподход дает сбой NSUnknownKeyException.
arlomedia
4

Помимо решений, упомянутых Дэном Фабуличем , XWebView - еще одно решение. [WKWebView loadFileURL: allowingReadAccessToURL:] будет осуществляться за счет расширения .

soflare
источник
1
Я решил использовать XWebView после того, как посмотрел другие способы решения этой проблемы. XWebView - это фреймворк, реализованный на Swift, но у меня не было проблем с его использованием в моем приложении iOS 8 Objective-C.
chmaynard
4

Я пока не могу комментировать, поэтому публикую это как отдельный ответ.

Это объективная версия решения nacho4d . Лучший обходной путь, который я когда-либо видел.

- (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
источник
1
Я не могу заставить это работать на iOS 8 в симуляторе. Я хочу поместить .png в / tmp / www, а затем использовать тег img в моем html. Что мне следует использовать для тега img src?
user3246173 04
было именно то, что мне нужно. Спасибо!
Tinkerbell
3

В случае, если вы пытаетесь отобразить локальное изображение в середине более крупной строки HTML, например:, <img src="file://...">оно все еще не отображается на устройстве, поэтому я загрузил файл изображения в NSData и смог отобразить его, заменив строку src на сами данные. Пример кода, помогающий создать строку HTML для загрузки в WKWebView, где результат - это то, что заменит то, что находится внутри кавычек 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];
patrickd105
источник
в быстрой версии добавьте точку с запятой перед base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil
Спасибо, это единственное, что у меня сработало, потому что я загружаю файл .gif во временную папку на устройстве, а затем загружаю этот файл в WKWebViewкак <img>внутри HTML-строки.
Г-н Зистем,
1

Я использую ниже. Есть некоторые дополнительные вещи, над которыми я работаю, но вы можете видеть, где я закомментировал loadRequest и заменяю вызов loadHTMLString. Надеюсь, это поможет, пока они не исправят ошибку.

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
Кажется, это работает только для файлов HTML, но не для других ресурсов.
Лим Тай Чин
1

Для тех, кто должен решить эту проблему в iOS8:

Если ваша страница несложная, вы можете сделать ее одностраничным приложением.

Другими словами, чтобы встроить все ресурсы в html файл.

Для этого: 1. скопируйте содержимое вашего js / css файла в / tags в html файле соответственно; 2. Преобразуйте файлы изображений в формат SVG, чтобы заменить соответствующие файлы. 3. загрузите страницу, как раньше, используя [webView loadHTMLString: baseURL:], например

Это немного отличалось от стиля изображения svg, но оно не должно сильно блокировать вас.

Казалось, что производительность рендеринга страницы немного снизилась, но было достойно, чтобы такой простой обходной путь работал под iOS8 / 9/10.

firebear
источник
0

В той же строке GCDWebServer я использую SImpleHttpServer ( http://www.andyjamesdavies.com/blog/javascript/simple-http-server-on-mac-os-x-in-seconds ), а затем loadRequest с локальным хостом URL - адрес. При таком подходе вам не нужно добавлять какую-либо библиотеку, но файлы веб-сайта не будут в пакете, поэтому он не будет доставлен. Из-за этого это было бы более подходящим для случаев отладки.

ToUpper
источник
0

Мне удалось использовать веб-сервер PHP в OS X. Копирование во временный каталог / www не помогло мне. Python SimpleHTTPServer пожаловался на то, что хочет читать типы MIME, вероятно, проблема с песочницей.

Вот сервер, использующий 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

Решение @ nacho4d хорошее. Я хочу его немного изменить, но не знаю, как это изменить в вашем посте. Я положил это сюда, надеюсь, вы не против. Спасибо.

Если у вас есть папка www, есть много других файлов, таких как png, css, js и т. Д. Затем вам нужно скопировать все файлы в папку tmp / www. например, у вас есть такая папка www: введите описание изображения здесь

затем в 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)")
        }
    }
}

функция fileURLForBuggyWKWebView8 скопирована из @ 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
источник
-1

Попробуйте использовать

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Кажется, все еще работает. Все же.

Oleksii
источник
Спасибо, попробую.
Лим Тай Чин
3
Это работает только для самого HTML-файла, а не для ресурсов, верно?
Лим Тай Чин
1
К сожалению, да, кажется, загружается только файл HTML. Будем надеяться, что это просто ошибка, а не новое ограничение на загрузку локальных файлов. Пытаюсь найти эту ошибку в исходниках WebKit.
Oleksii
1
Я столкнулся с той же проблемой - файлы HTML загружаются, но изображения и другие ресурсы, находящиеся в локальной файловой системе, не загружаются. В консоли WebKit выдает ошибку «не разрешено загружать локальный ресурс». Я зарегистрировал ошибку для этого, радар № 17835098
mszaro