WKWebView tidak memuat file lokal di bawah iOS 8

123

Untuk beta iOS 8 sebelumnya, muat aplikasi web lokal (dalam Bundel) dan itu berfungsi dengan baik untuk keduanya UIWebViewdan WKWebView, dan saya bahkan mem-porting game web menggunakan WKWebViewAPI baru .

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

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

view.addSubview(webView)

Tapi di beta 4, saya hanya mendapat layar putih kosong ( UIWebViewmasih berfungsi), sepertinya tidak ada yang dimuat atau dieksekusi. Saya melihat kesalahan di log:

Tidak dapat membuat ekstensi kotak pasir untuk /

Ada bantuan untuk membimbing saya ke arah yang benar? Terima kasih!

Lim Thye Chean
sumber
Coba juga menambahkan webView ke hierarki tampilan di viewDidLoad, dan muat permintaan di viewWillAppear. WKWebView saya masih berfungsi, tapi begitulah cara saya memilikinya. Mungkin WebView memiliki pengoptimalan untuk tidak memuat permintaan jika tidak ada dalam hierarki tampilan?
rvijay007
Selesai (view.addView di viewDidLoad dan loadRequest di viewWillAppear), dan saya mendapatkan layar putih yang sama dan pesan kesalahan yang sama.
Lim Thye Chean
9
Ini sepertinya hanya terjadi pada perangkat karena pada simulator berfungsi dengan baik. Saya menggunakan Objc.
GuidoMB
1
Ini tetap menjadi masalah di XCode 6 - beta 7. Solusi sementara saya adalah menggunakan github.com/swisspol/GCDWebServer untuk melayani file lokal.
GuidoMB
2
Ada yang mengujinya di bawah iOS 8.0.1?
Lim Thye Chean

Jawaban:

106

Mereka akhirnya memecahkan bug tersebut! Sekarang kita bisa menggunakan -[WKWebView loadFileURL:allowingReadAccessToURL:]. Rupanya perbaikan itu bernilai beberapa detik dalam video WWDC 2015 504 Memperkenalkan Pengontrol Tampilan Safari

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

Untuk iOS8 ~ iOS10 (Swift 3)

Seperti jawaban Dan Fabulish menyatakan ini adalah bug WKWebView yang tampaknya tidak akan diselesaikan dalam waktu dekat dan seperti yang dia katakan ada penyelesaian :)

Saya menjawab hanya karena saya ingin menunjukkan hasil kerja di sini. Kode IMO yang ditampilkan di https://github.com/shazron/WKWebViewFIleUrlTest penuh dengan detail tidak terkait yang mungkin tidak menarik bagi kebanyakan orang.

Solusinya adalah 20 baris kode, penanganan kesalahan dan komentar disertakan, tidak perlu server :)

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
}

Dan bisa digunakan sebagai:

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
sumber
2
Beberapa modifikasi untuk itu untuk menyalin seluruh folder, gambar dan semuanya. let orgFolder = NSBundle.mainBundle().resourcePath! + "/www"; var newFilePath = pathForBuggyWKWebView(orgFolder) self.loadingWebView!.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(newFilePath!+"/Loading.html")!))
Piwaf
1
Solusi Piwaf bekerja sedikit lebih baik untuk saya, perhatikan bahwa itu harus "self.webView" dan bukan "self.loadingWebView" mengingat contoh di atas
Patrick Fabrizius
2
Terima kasih @ nacho4d. tldr; solusi folder / tmp tidak akan bekerja pada 8.0 tetapi akan bekerja pada 8.0.2. Saya mengalami kesulitan mendapatkan solusi ini untuk bekerja pada perangkat pengujian saya dan akhirnya mengkloning repo shazron untuk mencobanya. Itu juga tidak berhasil. Ternyata solusi shazron tidak berfungsi pada versi iOS perangkat saya menjalankan 8.0 (12A366) di iPhone 6 Plus. Saya mencobanya di perangkat (iPad Mini) yang menjalankan iOS 8.0.2 dan berfungsi dengan baik.
jvoll
1
itu harus membiarkan _ = mencoba? fm.removeItemAtURL (dstURL) daripada let _ = try? fileMgr.removeItemAtURL (dstURL)
DàChún
3
Hanya untuk situs web sederhana. Jika Anda menggunakan ajax atau memuat tampilan lokal melalui angular, diharapkan "Permintaan asal lintas hanya didukung untuk HTTP". Satu-satunya fallback Anda adalah pendekatan server web lokal yang saya tidak suka karena terlihat di jaringan lokal. Ini perlu dicatat di pos, menghemat beberapa jam.
jenson-button-event
83

WKWebView tidak dapat memuat konten dari file: URL melalui loadRequest:metodenya. http://www.openradar.me/18039024

Anda dapat memuat konten melalui loadHTMLString:, tetapi jika baseURL Anda adalah file: URL, maka itu tetap tidak akan berfungsi.

iOS 9 memiliki API baru yang akan melakukan apa yang Anda inginkan , [WKWebView loadFileURL:allowingReadAccessToURL:] .

Ada solusi untuk iOS 8 , yang ditunjukkan oleh shazron di Objective-C di sini https://github.com/shazron/WKWebViewFIleUrlTest untuk menyalin file ke /tmp/wwwdan memuatnya dari sana .

Jika Anda bekerja di Swift, Anda dapat mencoba sampel nachos4d sebagai gantinya. (Ini juga jauh lebih pendek dari sampel shazron, jadi jika Anda mengalami masalah dengan kode shazron, cobalah sebagai gantinya.)

Dan Fabulich
sumber
1
Satu solusi (disebutkan di sini: devforums.apple.com/message/1051027 ) adalah memindahkan konten ke tmp dan mengaksesnya dari sana. Tes cepat saya tampaknya menunjukkan bahwa itu berhasil ...
Mike M
6
Tidak diperbaiki di 8.1.
matt
1
Memindahkan file ke / tmp bekerja untuk saya. Tapi ... Ya Tuhan, bagaimana ini bisa melalui pengujian?
Greg Maletic
1
Itu sampel adalah cara terlalu kode BANYAK hanya untuk demo. File yang akan dibaca harus ada di dalam /tmp/www/. Gunakan NSTemporaryDirectory()dan NSFileManageruntuk membuat wwwdirektori (karena tidak ada direktori seperti itu secara default). Kemudian salin file Anda di sana dan sekarang baca file ini :)
nacho4d
2
8.3 dan masih belum diperbaiki ...?
ninjaneer
8

Contoh cara menggunakan [WKWebView loadFileURL: allowReadAccessToURL:] di iOS 9 .

Saat Anda memindahkan folder web ke sebuah proyek, pilih "Buat referensi folder"

masukkan deskripsi gambar di sini

Kemudian gunakan kode seperti ini (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)
  }
}

Dalam file html gunakan jalur file seperti ini

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

tidak seperti ini

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

Contoh direktori yang dipindahkan ke proyek xcode.

masukkan deskripsi gambar di sini

Markus T.
sumber
6

Solusi sementara: Saya menggunakan GCDWebServer , seperti yang disarankan oleh GuidoMB.

Saya pertama kali menemukan jalur folder "www /" yang saya bundel (yang berisi "index.html"):

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

... lalu mulai seperti ini:

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

Untuk menghentikannya:

[_webServer stop];
_webServer = nil;

Performa tampak bagus, bahkan di iPad 2.


Saya melihat crash setelah aplikasi masuk ke latar belakang, jadi saya menghentikannya applicationDidEnterBackground:dan applicationWillTerminate:; Saya memulai / memulai ulang application:didFinishLaunching...dan applicationWillEnterForeground:.

EthanB
sumber
1
Menggunakan "server" mungkin memakan manfaat kinerja yang WKWebViewdiberikan UIWebView. Sebaiknya tetap dengan API lama sampai ini diperbaiki.
ray
@ray Bukan dengan Aplikasi Satu-Halaman.
EthanB
Saya telah membuat GCDWebServer berfungsi juga, di bentukan internal aplikasi saya. Dan jika ada banyak javascript di aplikasi Anda, server sangat berharga. Tetapi ada masalah lain yang mencegah saya menggunakan WKWebView sekarang, jadi saya berharap ada peningkatan di iOS 9.
Tom Hamming
Dan bagaimana cara menampilkannya di WebView? Saya benar-benar tidak mengerti untuk apa GCDWebServer itu?
chipbk10
1
Alih-alih mengarahkan tampilan web ke "file: // .....", Anda mengarahkannya ke "http: // localhost: <port> / ...".
EthanB
5
[configuration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

Ini memecahkan masalah untuk saya iOS 8.0+ dev.apple.com

juga ini tampaknya bekerja dengan baik juga ...

NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
                       stringByAppendingPathComponent:@"htmlapp/FILE"];
[self.webView
    loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
    allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
nullqube
sumber
alih-alih FILE, Anda juga dapat memasukkan DIR.
nullqube
Ini adalah sebuah penemuan besar. Saya tidak tahu mengapa tidak dipilih lebih tinggi.
plivesey
Posting ini memiliki info lebih lanjut (termasuk tautan ke sumber webkit): stackoverflow.com/questions/36013645/…
plivesey
konfigurasi.preferences setValue akan memberi error pada iOS 9.3
Vignesh Kumar
Saya menggunakan iOS 13 SDK tetapi mencoba menjaga kompatibilitas dengan iOS 8, dan allowFileAccessFromFileURLspendekatannya macet NSUnknownKeyException.
arlomedia
4

Selain solusi yang disebutkan oleh Dan Fabulich, XWebView adalah solusi lain. [WKWebView loadFileURL: allowingReadAccessToURL:] yang dilaksanakan melalui ekstensi .

soflare
sumber
1
Saya memutuskan untuk menggunakan XWebView setelah melihat solusi lain untuk masalah ini. XWebView adalah kerangka kerja yang diimplementasikan di Swift tetapi saya tidak punya masalah menggunakannya di aplikasi iOS 8 Objective-C saya.
chmaynard
4

Saya belum bisa berkomentar, jadi saya memposting ini sebagai jawaban terpisah.

Ini adalah versi objektif-c dari solusi nacho4d . Solusi terbaik yang pernah saya lihat sejauh ini.

- (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
sumber
1
Saya tidak bisa mendapatkan ini berfungsi di iOS 8 dalam simulator. Saya ingin meletakkan .png di / tmp / www dan kemudian menggunakan tag img di html saya. Apa yang harus saya gunakan untuk src tag img?
pengguna3246173
adalah apa yang saya butuhkan. Terima kasih!
Tinkerbell
3

Jika Anda mencoba untuk menampilkan gambar lokal di tengah string HTML yang lebih besar seperti <img src="file://...">:, gambar tersebut masih tidak muncul di perangkat, jadi saya memuat file gambar ke NSData dan dapat menampilkannya dengan mengganti string src dengan datanya sendiri. Kode contoh untuk membantu membangun string HTML untuk dimuat ke WKWebView, di mana hasilnya akan menggantikan apa yang ada di dalam tanda kutip src = "":

Cepat:

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
sumber
dalam versi cepat, tambahkan titik koma sebelum base64. result += "data:image/" + attachmentType + ";base64," + base64String
shahil
Terima kasih, ini adalah satu-satunya hal yang berhasil untuk saya, karena saya mengunduh file .gif ke folder temp pada perangkat, dan kemudian memuat file itu ke dalam WKWebViewsebagai <img>dalam HTMLstring.
Tn. Zystem
1

Saya menggunakan di bawah ini. Memiliki beberapa hal tambahan yang sedang saya kerjakan tetapi Anda dapat melihat di mana saya mengomentari loadRequest dan saya mengganti panggilan loadHTMLString. Semoga ini bisa membantu sampai mereka memperbaiki 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
sumber
3
Ini tampaknya hanya berfungsi untuk file HTML tetapi tidak untuk sumber daya lainnya.
Lim Thye Chean
1

Untuk siapa yang harus mengatasi masalah ini di bawah iOS8:

Jika halaman Anda tidak rumit, Anda dapat memilih untuk menjadikan halaman tersebut sebagai Aplikasi Halaman Tunggal.

Dengan kata lain, untuk menyematkan semua sumber daya ke dalam file html.

Yang harus dilakukan: 1. salin konten file js / css Anda ke / tags di file html masing-masing; 2. konversikan file gambar Anda menjadi svg untuk menggantikan yang sesuai. 3. memuat halaman seperti sebelumnya, menggunakan [webView loadHTMLString: baseURL:], misalnya

Ini sedikit berbeda dengan menata gambar svg, tetapi itu seharusnya tidak terlalu menghalangi Anda.

Tampaknya kinerja perenderan halaman sedikit menurun, tetapi solusi sederhana seperti itu layak bekerja di iOS8 / 9/10.

senjata api
sumber
0

Saya telah berhasil menggunakan server web PHP di OS X. Menyalin ke direktori sementara / www tidak berhasil untuk saya. Python SimpleHTTPServer mengeluh tentang keinginan membaca jenis MIME, mungkin masalah sandboxing.

Berikut server yang menggunakan 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
sumber
0

@ nacho4d solusinya bagus. Saya ingin mengubahnya sedikit tetapi saya tidak tahu bagaimana mengubahnya di posting Anda. Jadi saya taruh di sini, saya harap Anda tidak keberatan. Terima kasih.

Jika Anda memiliki folder www, ada banyak file lain seperti png, css, js dll. Kemudian Anda harus menyalin semua file ke folder tmp / www. misalnya, Anda memiliki folder www seperti ini: masukkan deskripsi gambar di sini

lalu di 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)")
        }
    }
}

file fungsiURLForBuggyWKWebView8 disalin dari @ 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
sumber
-1

Coba gunakan

[webView loadHTMLString:htmlFileContent baseURL:baseURL];

Sepertinya itu masih berfungsi. Namun.

Oleksii
sumber
Terima kasih saya akan mencobanya.
Lim Thye Chean
3
Ini hanya berfungsi untuk file HTML itu sendiri, bukan sumber dayanya, bukan?
Lim Thye Chean
1
Sayangnya ya, sepertinya hanya file HTML yang dimuat. Mari berharap ini hanya bug, dan bukan batasan baru untuk memuat file lokal. Saya mencoba menemukan bug ini di sumber WebKit.
Oleksii
1
Saya mengalami masalah yang sama - file HTML dimuat tetapi gambar dan sumber daya lain yang ada di sistem file lokal tidak dimuat. Di konsol, WebKit menampilkan kesalahan "tidak diizinkan untuk memuat sumber daya lokal". Saya melaporkan bug untuk ini, radar # 17835098
mszaro