WKWebView使用loadHTMLString(_, baseURL:)无法加载图片和CSS

56

Apple的建议:

在运行于iOS 8及更高版本的应用程序中,应使用WKWebView类来替代UIWebView。

因此,我已经将旧的UIWebView替换为全新的WKWebView。但我原以为这是一项简单的任务(仅需交换类并替换委托方法),结果却变成了一个真正的混乱。

问题

当我使用以下代码加载HTML字符串时:

loadHTMLString(String, baseURL: URL?)

网页视图加载并呈现纯HTML,但它不会加载任何在htmlString内引用的图像或CSS文件。

这只会发生在真实设备上
在模拟器中,所有引用的资源都会正确加载。

Simulator Real Device

示例

我在我的视图控制器类中定义了一个简单的htmlString

let imageName = "image.png"

let libraryURL: URL // The default Library URL

var htmlString: String {
    return "<html> ... <img src=\"\(imageName)\" /> ... </html>"
    // "..." represents more valid HTML code incl. header and body tags
}

图片存储在根Library文件夹中,因此它的URL为:

let imageURL = libraryURL.appendingPathComponent(imageName)

现在我将htmlString加载到Web视图中:

webView.loadHTMLString(htmlString, baseURL: libraryURL)

即使设置了baseURL,它也无法加载图像。

解决方案的想法

  1. 也许WKWebView有问题解析相对路径,因此我的第一个想法是在HTML字符串中使用绝对路径
    → ❌ 不起作用。

  2. 另一个SO帖子的两个答案建议在iOS 9+中使用
    loadFileURL(URL, allowingReadAccessTo: URL)
    而不是loadHTMLString(...)
    → ✅ 可以使用该方法。

然而,我不能使用解决方案2,因为我的HTML文件是加密的,并且解密后的文件不能存储在磁盘上。

问题

是否有任何方法可以使用WKWebView加载本地资源,如图像和样式?

loadHTMLString(String, baseURL: URL?)

这是功能问题吗?还是iOS 9+中仍然存在一个错误?

(我简直无法相信Apple提供并推荐使用的Web视图无法从HTML字符串内部加载任何本地Web内容!)


尝试这个:https://dev59.com/o10Z5IYBdhLWcg3wnBb_ - vijay
尝试这个:https://dev59.com/o10Z5IYBdhLWcg3wnBb_ - vijay
我遇到了同样的问题。尝试使用“loadFileURL(URL,allowingReadAccessTo:URL)”。但对我来说仍然无效。我的图片仍无法加载。你是如何解决这个问题的? - yaali
2
你是怎么让它运作的? - ozd
@Solomiya,你找到可行的解决方案了吗? 你能帮我解决一个类似的问题吗?https://stackoverflow.com/questions/63800637/unable-to-load-html-with-only-images-in-wkwebview-ios-13-swift-5 - iMinion
显示剩余4条评论
12个回答

16

在没有看到您的实际项目之前,很难给出百分之百确定的建议。

然而:

class ViewController: UIViewController {

    var webView = WKWebView()

    override func viewDidLoad() {
        super.viewDidLoad()
        webView.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "webView" : webView
        ]
        view.addSubview(webView)
        var constraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[webView]|", options: [.AlignAllLeading, .AlignAllTrailing], metrics: nil, views: views)
        constraints.appendContentsOf(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: [.AlignAllTop, .AlignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activateConstraints(constraints)

        let path = NSBundle.mainBundle().pathForResource("ios - WKWebView fails to load images and CSS using loadHTMLString(_, baseURL_) - Stack Overflow", ofType: "htm")
        let url = NSURL(fileURLWithPath: path!)
        webView.loadHTMLString(try! String(contentsOfURL: url), baseURL: url.URLByDeletingLastPathComponent)

        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

我认为这里的关键是 baseUrl 参数,你需要正确设置它。在我的情况下,我使用了不带最后路径组件的html url,例如包含文件夹。这在设备和模拟器上都可以正常工作 - 请查看设备快照。我已经将示例项目上传到https://github.com/soxjke/WKWebViewTest,所以你可以看一下(我从git中删除了代码签名信息)。

设备快照

因此,简要概括一下-方法正常运行,功能正常,只是你在某些方面做错了。为了帮助你找出解决方案中的问题,我会添加一些建议: 1. 记住,模拟器文件系统是不区分大小写的,设备文件系统是区分大小写的。因此,如果你在html中使用小写字母作为文件名,在设备上就无法工作。8fFsD.png != 8ffsd.png 2. 记住,在复制资源时,XCode会忽略你的文件夹结构。因此,如果你的html中有<img src="./img/1.png">,而你的XCode项目的文件夹结构如下:

test.htm

img/

    1.png 
    2.png

构建后,它将被压平,因此test.htm、1.png和2.png将位于同一级别

test.htm
1.png 
2.png

我几乎确定,在你验证了这两个假设之后,你会使这种方法工作。


1
感谢在 GitHub 上分享样例项目。有了它,我已经能够让网页视图在我的 iPhone 上加载资源了。我还没有完全弄清楚我的实现有什么问题,为什么只在模拟器上运行,但是我一有时间就会调查,并在这个页面的另一个答案中发布我的发现。 - Mischa
很高兴我的回答有所帮助。正如我所写的,我建议首先检查文件名和大小写,然后再检查文件夹结构。99%的情况下,您会找到问题所在。 - Petro Korienev
1
问题已解决。谢谢! - TotoroTotoro
请问您能否帮我检查一下这个问题,其中HTMLString对我来说是必需的,我遇到了同样的问题。https://stackoverflow.com/questions/66050435/images-from-document-directory-is-not-visible-in-webview-it-is-working-fine-for - PJR

13

我今天遇到了这个问题,找到了解决方案并可能找到了原因:

loadHTMLString(String, baseURL: URL?) 

据我所知,此函数不允许呈现的HTML访问本地媒体,因为这将存在注入风险,这可能会使呈现的HTML访问和操纵您的本地文件系统。对于来自任何地方或任何人提供的HTML字符串而言,情况更加危险。

loadFileURL(URL, allowingReadAccessTo: URL)

使用此函数,您可以将WKWebview指向您在FileManager中的html文件,并使用'allowingReadAccessTo'指定包含文件夹。由于html存储在FileManager中,因此它将允许呈现的HTML访问本地存储的媒体。

如果出于某种原因您没有将html文件保存在本地(我认为您已经保存了),您可以将html字符串写入.html文件,然后指向该文件的URL。但是,这只是规避Apple的保护措施,因此自行承担风险(不要这样做)。

这只是对我有效的解决方案和我理解为什么我们遇到这个问题的原因。

编辑#1:错别字。

编辑#2:我后来发现另一个细微差别。请注意,在声明“allowingReadAccessTo:” URL时,如果HTML本身需要访问父文件夹中的内容(即.css,.js文件),则需要指定父文件夹,而不一定是HTML本身的位置,这将隐式允许访问所需的子文件夹。对我来说,在物理设备上才出现了这个问题,在模拟器中运行时似乎没有影响,可能是模拟器和物理设备上权限工作方式之间的另一个差异。


2
就是这样!在从UIWebView迁移到WKWebView后,CSS停止工作了。现在我将HTML保存到文件中,并使用allowingReadAccessTo:读取文件URL,一切都可以正常加载。 - green0range
@ green0range 如果您能帮我解决这个问题,那就太好了。 https://stackoverflow.com/questions/63800637/unable-to-load-html-with-only-images-in-wkwebview-ios-13-swift-5 - iMinion

3

你应该可以使用已经找到的函数在WKWebViews中使用本地图片和CSS文件(JavaScript文件也一样)。我猜问题可能出在你的baseURL变量上。

2017年7月5日更新:

我已经完全更新了代码,来自我的另一个SO答案,曾经与我的这个答案链接在一起。我有一个可以使用loadHTMLString().loadFileURL()的工作项目。


1
你在真实设备上试过吗?因为这基本上就是我所做的。baseURL应该是解析相对路径的位置,但问题似乎与此无关,因为我也尝试在HTML中引用绝对路径,但仍然不起作用。 - Mischa
(顺便说一句:一旦你弄清了怪癖,一切都会很好。本来就不应该有任何怪癖!) - Mischa

3
个人而言,我不得不切换到使用XWebView,因为WKWebView默认行为不允许加载本地文件。XWebView通过在后台加载本地Web服务器并将本地流量引导通过它来欺骗WKWebView。 (XWebView基于WKWebView构建)。
看起来有点繁琐,但这是我最终不得不做的事情。

谢谢你的建议。我在其他帖子中也读到过这个Web服务器技巧。但是,使用外部库来设置本地Web服务器只是为了呈现一些基本的HTML,这似乎真的有点过度杀伤力了 - 这是Web视图的主要目的! - Mischa

3
我也进行了类似的实验,但出现了一些限制,问题似乎在于除非baseURL引用应用程序包,否则无法解析路径。例如,在应用程序文档中放置某些东西是不起作用的。

编辑:我已经为此提交了一个雷达报告rdar://29130863


我有和rdar一模一样的问题。你记得你最终做了什么吗? :) - ozd

3

尝试使用以下方式创建baseURL

let baseURL = URL(fileURLWithPath: "#path#")

而不是:

let baseURL = URL(string: "#path#")

主要区别在于第一种方法在路径前添加了file://前缀。


2
您可以对图像进行Base64编码...我知道这个方法可行。不确定它是否适用于您的使用情况。
有点有趣,我刚遇到了相反的问题 - 从Base64编码转换为图像文件。

听起来像是一个聪明的解决方案!我会尝试一下。然而,在我的情况下,这真的感觉像是肮脏的黑客行为,因为我将不得不解析HTML,找到所有图像URL,将它们编码为base64数据并用数据替换URL。 - Mischa
是的,我能理解。在我的情况下,我已经有了要放入HTML中的图像,所以这样做会更简洁一些。 - moliveira
我已经尝试过对图像进行Base64编码,但仍然无法正常工作。https://github.com/starkindustries/Print2PDFTest - Zion

2
当我使用UIWebview时,我将baseURL设置为,
let baseUrl = NSURL(string: Bundle.main.path(forResource: "cms", ofType: "html")!)! as URL

webView.loadHTMLString(bodyPage, baseURL: baseUrl)

但对于WKWebView,我使用了baseURL作为

let baseUrl = Bundle.main.bundleURL

webView.loadHTMLString(bodyPage, baseURL: baseUrl)

这对我来说是有效的。


1

我花了一些时间才弄明白,但是根据这个答案,我终于搞定了:

https://dev59.com/B7joa4cB1Zd3GeqPGeM4#73519282

试试这个:

let htmlPath = URL(fileURLWithPath: "")
let htmlDirectory = htmlPath.deletingLastPathComponent() 
let htmlString = try! String(contentsOfFile: htmlPath.path, encoding: .utf8) 

let baseURL = URL(fileURLWithPath: htmlDirectory)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[.zero]

webView.loadFileURL(htmlPath, allowingReadAccessTo: documentsDirectory)
webView.loadHTMLString(htmlString, baseURL: baseURL)


谢谢兄弟,这对我有用。 - Bijender Singh Shekhawat

1

我知道这个问题已经很老了,但是我遇到了完全相同的问题,花费了数小时的尝试甚至才找到了这个带有同样问题(Xamarin Forms App)的线程。

我的问题是:解析远程HTML内容为字符串,并添加本地保存的图像(也是动态下载的,不是应用程序资源)。在模拟器上一切正常,但在实际设备上本地图像不显示(也没有任何错误指示,只有一个空白框架)。 Xamarin Webview还提供了“BaseURL”选项,但这并没有帮助解决问题,自定义iOS wkWebView也不能使用BaseURL。

正如Scott所指出的唯一可行的解决方案是将HTML写入文件中,然后使用“LoadFileUrl”函数并允许基目录的读取访问。这也适用于HTML中的绝对文件路径(不仅是相对于基本目录,而是任何地方都可以)。

现在,我的自定义webview渲染器用于加载Web和本地内容如下:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
    base.OnElementPropertyChanged(sender, e);
    NSUrl baseURL = new NSUrl(App.dirNews, true);
    string viewFile = Path.Combine(App.dirNews, "view.html");
    NSUrl fileURL = new NSUrl(viewFile, false);

    switch (e.PropertyName) {
        case "Url":
            System.Console.WriteLine("--- Loading Web page ---");
            System.Console.WriteLine("--- " + Element.Url + " ---");
            NSUrlRequest myRequest = new NSUrlRequest(new NSUrl(Element.Url), NSUrlRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, 120);
            Control.LoadRequest(myRequest);
            break;

        case "HTML":
            System.Console.WriteLine("--- Showing HTTP content ---");
            File.WriteAllText(viewFile, Element.HTML, System.Text.Encoding.UTF8);
            Control.LoadFileUrl(fileURL, baseURL);
            break;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接