Search code examples
iosswiftxcodefont-facewkwebview

Xcode: Load custom locally stored font for remote content in WKWebView


I'm using the newest version of Xcode and Swift.

I have the following code to load an HTML string and use a custom font, I prior imported in Xcode:

let html = """
        <html>
        <body>
        <head>
        <style>
        @font-face
        {
            font-family: 'Blastimo';
            src: local('Blastimo'), url('Blastimo.ttf') format('truetype');
        }
        </style>
        </head>
        <h1 style="font-family:'Blastimo';font-size:50px;">This is my test!</h1>
        </body>
        </html>
        """
        webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)

The following code allows me to load remote content, e.g. from my web server:

if let url = URL(string: "https://www.example.com") {
            let request = URLRequest(url: url)
            webView.load(request)
        }

But, what I want to reach is: Load remote content, as in second code, but pass my locally stored custom font to this, as in the first code.

Means: I know how to load an HTML string with a locally stored font and I know how to load remote content without a locally stored font. But I don't know how to load remote content with a locally stored font.

I tried:

webView.load(request, baseURL: Bundle.main.resourceURL)

but this isn't working. It throws an error.

I tried so much but without success. My last idea is to get the source code from the remote content, convert it to a string, add font-face style to it and load it with webView.loadHTMLString. This will allow me to display the remote content while still being able to load this with webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL) to pass the locally stored font to it.

Is there really no other way to do this? Is there no way to use my locally stored font when using webView.load(request)?


Solution

  • I found a solution now. Maybe not the best but it's working.

    First, I converted the Blastimo.ttf file to a base64 encoded CSS embedded font with this converter.

    This gives me the following (or similar):

    @font-face {
        font-family: 'Blastimo';
        src: url(data:application/x-font-woff;charset=utf-8;base64,HERE_COMES_THE_QUITE_LONG_BASE64_CODE_CREATED_BY_THE_CONVERTER) format('woff');
        font-weight: normal;
        font-style: normal;
    }
    

    I imported/embedded a file called fonts.css with this code (the code above from the converter) to my Xcode project. Add to targets should be activated when importing/embedding.

    In my ViewController.swift file I have the following code (e.g. in viewDidLoad()) to load the remote content and call the functions for the local CSS file:

        if let url = URL(string: "https://www.example.com") {
        let request = URLRequest(url: url)
        webView.load(request)
        }
        injectToPage()
    

    Additionally, these three functions have to be added somewhere to the ViewController.swift file:

        private func readFileBy(name: String, type: String) -> String {
            guard let path = Bundle.main.path(forResource: name, ofType: type) else {
                return "Failed to find path"
            }
            
            do {
                return try String(contentsOfFile: path, encoding: .utf8)
            } catch {
                return "Unkown Error"
            }
        }
        func injectToPage() {
            let cssFile = readFileBy(name: "fonts", type: "css")
            let cssStyle = """
                javascript:(function() {
                var parent = document.getElementsByTagName('head').item(0);
                var style = document.createElement('style');
                style.innerHTML = window.atob('\(encodeStringTo64(fromString: cssFile)!)');
                parent.appendChild(style)})()
            """
            let cssScript = WKUserScript(source: cssStyle, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
            webView.configuration.userContentController.addUserScript(cssScript)
        }
        private func encodeStringTo64(fromString: String) -> String? {
            let plainData = fromString.data(using: .utf8)
            return plainData?.base64EncodedString(options: [])
        }
    

    Now, I can use the locally in the app stored font in my WKWebView although the content is loaded remotely.

    In my case, I added this to the (html or php) file, that's loaded remotely from my web server:

    <style>
    *   {
        font-family:'Blastimo';
    }
    </style>
    

    Note: The file you're loading remotely needs to have <head></head> element, since the font is injected at the end of this.