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 in viewDidLoad and loadRequest in viewWillAppear), і я отримав той самий білий екран та те саме повідомлення про помилку.
Lim Thye Chean

9
Це, здається, відбувається на пристрої, оскільки на тренажері працює чудово. Я, до речі, використовую Objc.
GuidoMB

1
Це залишається проблемою в XCode 6 - бета-версія 7. Моє тимчасове рішення полягало у використанні github.com/swisspol/GCDWebServer для обслуговування локальних файлів.
GuidoMB

2
Хтось тестував його під iOS 8.0.1?
Lim Thye Chean

Відповіді:


106

Вони нарешті вирішили помилку! Тепер ми можемо використовувати -[WKWebView loadFileURL:allowingReadAccessToURL:]. Очевидно, виправлення коштувало декількох секунд у відеозаписі WWDC 2015 504 Представлення контролера перегляду Safari

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

2
Деякі модифікації для нього, щоб скопіювати всю папку, зображення та все. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf

1
Рішення Piwaf для мене спрацювало трохи краще, зауважте, що воно повинно бути "self.webView", а не "self.loadingWebView", враховуючи приклад вище, хоча
Патрік Фабризіус

2
Дякую @ nacho4d. tldr; рішення папки / tmp не буде працювати на 8.0, але буде на 8.0.2. У мене виникли проблеми з тим, як це рішення працювало на моєму тестовому пристрої, і врешті-решт клонував репортаж шазрона, щоб спробувати. Це теж не спрацювало. Виявляється, що рішення шазрона не працює на версії iOS, на моєму пристрої працює iPhone 8.0 (12A366) на iPhone 6 Plus. Я спробував це на пристрої (iPad Mini) під управлінням iOS 8.0.2, і він працює чудово.
jvoll

1
слід дозволити _ = спробувати? fm.removeItemAtURL (dstURL) замість того, щоб _ = спробувати? fileMgr.removeItemAtURL (dstURL)
DàChún

3
Тільки для простих веб-сайтів. Якщо ви використовуєте ajax або завантажуєте локальні представлення через кутовий, очікуйте, що "Запити між походженнями підтримуються лише для HTTP". Ваш єдиний запас - це підхід до локального веб-сервера, який мені не подобається, оскільки він видно в локальній мережі. Це потрібно зауважити в публікації, заощаджуйте людей кілька годин.
jenson-button-event

83

WKWebView не може завантажити вміст з файлу: URL-адреси за допомогою його loadRequest:методу. http://www.openradar.me/18039024

Ви можете завантажувати вміст через loadHTMLString:, але якщо ваш базовийURL - це файл: URL, він все одно не працюватиме.

iOS 9 має новий API, який буде робити те, що ви хочете [WKWebView loadFileURL:allowingReadAccessToURL:] ,.

Існує рішення для iOS 8 , продемонстрований шазроном в Objective-C тут https://github.com/shazron/WKWebViewFIleUrlTest для копіювання файлів /tmp/wwwі завантаження їх звідти .

Якщо ви працюєте в Swift, ви можете спробувати зразок nachos4d . (Це також набагато коротше, ніж зразок шазрона, тому, якщо у вас виникають проблеми з кодом шазрона, спробуйте замість цього.)


1
Одне вирішення (згадане тут: devforums.apple.com/message/1051027 ) - перемістити вміст у tmp та отримати доступ до нього звідти. Мій швидкий тест, схоже, свідчить про те, що він працює ...
Майк М

6
Не зафіксовано в 8.1.
мат

1
Переміщення файлів у / tmp працює для мене. Але ... мій боже, як це пройшло через тестування?
Грег Малетич

1
Цей зразок - ШЛЯХ ТОЛЬКО коду лише для демонстрації. Файл, який слід прочитати, повинен бути всередині /tmp/www/. Використовуйте NSTemporaryDirectory()та NSFileManagerдля створення wwwкаталогу (тому що за замовчуванням такого каталогу немає). Потім скопіюйте свій файл туди і зараз прочитайте цей файл :)
nacho4d

2
8.3 і це все ще не виправлено ...?
ninjaneer

8

Приклад використання [WKWebView loadFileURL: enableReadAccessToURL:] на 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:.


1
Використання "сервера", ймовірно, з'їдає будь-які переваги від продуктивності, які WKWebViewнадає більше UIWebView. Можна також дотримуватися старого API, поки це не буде виправлено.
промінь

@ray Не з додатком для однієї сторінки.
EthanB

Я змусив GCDWebServer працювати також у внутрішніх версіях свого додатка. І якщо у вашому додатку багато javascript, сервер цілком того вартий. Але є й інші проблеми, які заважають мені зараз використовувати WKWebView, тому я сподіваюся на покращення iOS 9.
Том Хеммінг

І як це показати на WebView? Я дійсно не розумію, для чого призначений GCDWebServer?
chipbk10

1
Замість того, щоб вказувати веб-перегляд на "файл: // .....", ви вказуєте його на "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]
];

замість ФАЙЛу ви можете також поставити DIR.
nullqube

Це чудова знахідка. Я не знаю, чому вона не проголосувала вище.
плівесей

Це повідомлення має більше інформації ( в тому числі посилання на джерело WebKit): stackoverflow.com/questions/36013645 / ...
plivesey


Я використовую iOS 13 SDK, але намагаюся зберегти сумісність з iOS 8, і allowFileAccessFromFileURLsпідхід виходить з ладу NSUnknownKeyException.
arlomedia

4

Крім рішень, згаданих Деном Фабулічем, XWebView - це ще одне вирішення. [WKWebView loadFileURL: allowingReadAccessToURL:] буде здійснюватися за рахунок розширення .


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;
}

1
Я не можу зробити це для роботи на iOS 8 в тренажері. Я хочу поставити .png в / tmp / www, а потім використовувати тег img у своєму HTML. Що я повинен використовувати для тегів img src?
користувач3246173

було саме те, що мені було потрібно. Дякую!
Тінкербелл

3

У випадку, якщо ви намагаєтесь відобразити локальне зображення посередині більшого рядка HTML типу:, <img src="file://...">він все ще не відображається на пристрої, тому я завантажив файл зображення в NSData і зміг відобразити його, замінивши рядок src на самі дані. Зразок коду, який допоможе побудувати рядок HTML для завантаження у WKWebView, де результат - те, що замінить те, що знаходиться всередині лапок src = "":

Швидкий:

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:

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];

у швидкій версії додайте крапку з комою до бази64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil

Дякую, це єдине, що працювало для мене, тому що я завантажую .gif файл у папку temp на пристрій, а потім завантажую цей файл у WKWebViewяк <img>у HTMLstring.
Містер Zystem

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, але не для інших ресурсів.
Lim Thye Chean

1

Для того, хто повинен вирішити цю проблему під iOS8:

Якщо ваша сторінка не є складною, ви можете зробити її як додаток для однієї сторінки.

Іншими словами, вставити всі ресурси у файл html.

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

Це було трохи інше, як створити SVG-образ, але він не повинен вас сильно блокувати.

Здавалося, ефективність візуалізації сторінки дещо знизилася, але гідно було, щоб такий простий спосіб працював під iOS8 / 9/10.


0

У тому ж рядку GCDWebServer я використовую SImpleHttpServer ( http://www.andyjamesdavies.com/blog/javascript/simple-http-server-on-mac-os-x-in-seconds ), а потім завантажую Запит із localhost URL. При такому підході вам не потрібно додавати будь-яку бібліотеку, але файли веб-сайтів не будуть в комплекті, тому це не буде доступним. Через це це було б більш доречно для випадків налагодження.


0

Мені вдалося використати веб-сервер PHP в ОС 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)")
        }
    }
}

файл функціїURLForBuggyWKWebView8 копіюється з @ 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

Спробуйте використовувати

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Здається, це все ще працює. І все-таки.


Дякую, я спробую це.
Lim Thye Chean

3
Це працює лише для самого файлу HTML, а не для ресурсів, правда?
Lim Thye Chean

1
На жаль, так, здається, завантажується лише файл HTML. Будемо сподіватися, що це просто помилка, а не нове обмеження для завантаження локальних файлів. Я намагаюся знайти цю помилку у джерелах WebKit.
Олексій

1
У мене виникла та сама проблема - HTML-файли завантажуються, але зображення та інші ресурси, які знаходяться у локальній файловій системі, не завантажуються. У консолі WebKit видає помилку "заборонено завантажувати локальний ресурс". Я подав помилку на це, радіолокатор # 17835098
mszaro
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.