Folks,
I’m writing a private app to browse through HTML contents stored on a website using that website's API. Everything works perfectly, except when I want to add a custom CSS to pretty print the contents.
I’m using a WKWebView suitably piggybacked to a SwiftUI view, and tried and use a small JS script to inject the CSS into the loaded page. The method I found makes use of WKUserContentController and creates a user script which is (purportedly) loaded after the page is rendered.
But that doesn’t work. The JS script is injected alright, but the view does not update. Worse, if I select another HTML chunk from the list of available pages, the chunk loads fine but the CSS is not applied. So the view does update, but spurns the script.
Yet, if I switch to an HTML source view, which kills the WKWebView instance, and then bring it back in, the CSS miraculously activates.
So, am I wrong, or shall I deduce that WKUserContentController's scripts are executed only once, when the view appears? What's more puzzling is that the updateNSView method of the wrapping view (which itself calls loadHTMLString) is correctly called after the CSS has been injected, so that should result in a view update, but it doesn’t.
What gives?
For reference, this is the piggybacking object:
struct WebView: NSViewRepresentable {
typealias NSViewType = WKWebView
let webView: WKWebView
let wkUCC = WKUserContentController()
let wkConfig = WKWebViewConfiguration()
var HTML: String?
var zoom: Double
var CSS: String?
init(HTML: String?, CSS: String?, zoom: Double) {
wkConfig.userContentController = wkUCC
self.webView = WKWebView(frame: CGRect.zero, configuration: wkConfig)
self.HTML = HTML
self.zoom = zoom
self.CSS = CSS
if let css = CSS {
loadCSS(css: css)
}
}
func loadCSS (css: String) {
let script = """
var oldStyles = document.getElementsByTagName('style');
if (oldStyles != null) {
for (let oldStyle of oldStyles) {
document.head.removeChild(oldStyle);
}
}
var newStyle = document.createElement('style');
newStyle.innerHTML = '\(css)';
var result = document.head.appendChild(newStyle);
"""
.replacing(/\n/, with: "")
let userScript = WKUserScript(source: script,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true)
wkUCC.removeAllUserScripts()
wkUCC.addUserScript(userScript)
}
func makeNSView(context: Context) -> WKWebView {
return webView
}
func updateNSView(_ webView: WKWebView, context: Context) {
if var HTML = self.HTML {
webView.loadHTMLString(HTML, baseURL: nil)
webView.pageZoom = zoom
}
}
}
and the calling code in the wrapping view:
if displaySource {
TextEditor(text: Binding<String>(get: {item.HTML}, set:{_ in}))
.font(.system(size: zoom * 14, weight: .light, design: .monospaced))
.padding(.horizontal, 5)
} else {
WebView(HTML: item.HTML, CSS: CSS, zoom: zoom)
.padding(.horizontal, 5)
}
P.S: I tried to use various decorators such as @State and @Biding to shake things up, but to no avail.