WKWebView でロードした外部HTMLにカスタムフォントを適用する(無理やり)


WKWebView を使ってアプリ内のHTMLを表示する場合、下記のようなCSSでアプリに追加したカスタムフォント1を参照することができる(らしい)。

@font-face {
  font-family: 'CustomFontFamily';
  src: url('CustomFontFile.otf') format('opentype');
}

これは、HTMLとフォントが同じディレクトリに置いてある状態だから url('CustomFontFile.otf') を解決することができるということであって、アプリにカスタムフォントを追加しても WKWebView で使えるフォントが増えるわけではない(ということだと思う)。

よって、外部のHTMLを表示する場合は、当然、アプリ内のデータであるカスタムフォントを参照することはできない(未確認だけど、アプリ内のフルパスを書いてもセキュリティ的に無理なのでは)。

それでもカスタムフォントが使いたい

外部HTMLでもカスタムフォントを使う方法を考えてみた。

  • WKWebView はロードした HTML 内で、Swift のコード側から JavaScript を実行することができる。JavaScript が実行できるのであれば CSS を追加することもできる。
  • HTML からアプリ内部のデータにアクセスすることはできなくても、データそのものをインライン (Data URI スキーム) で挿入すればよいのでは。

実際のコード

iOS でも游ゴシックを使えるようにしてみるコード(検証用)。
あらかじめアプリ内にフォントデータを追加してある。

ViewController.swift
import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

    @IBOutlet weak var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        webView.navigationDelegate = self

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

    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        // アプリ内のフォントデータを Base64 でエンコードする
        let yuGothicMedium = getDataString(otf: "YuGothic-Medium")
        let yuGothicBold = getDataString(otf: "YuGothic-Bold")

        // エンコードしたフォントデータを Data URI スキームで埋め込んだ CSS
        let cssString = """
        @font-face {
            font-family: 'YuGothic';
            src: url('data:font/otf;base64,\(yuGothicMedium)') format('opentype');
            font-weight: normal;
        }
        @font-face {
            font-family: 'YuGothic';
            src: url('data:font/otf;base64,\(yuGothicBold)') format('opentype');
            font-weight: bold;
        }
        """

        // 上記 CSS を style 要素として追加する JavaScript
        // cssString は改行が入るのでテンプレートリテラル
        let customFontJs = """
        var style = document.createElement('style');
        style.innerHTML = `\(cssString)`;
        document.head.appendChild(style);
        """

        // 上記 JavaScript を実行する
        webView.evaluateJavaScript(customFontJs, completionHandler: nil)
    }

    func getDataString(otf: String) -> String {
        let path = Bundle.main.path(forResource: otf, ofType: "otf")
        let url = URL(fileURLWithPath: path!)
        let data = try! Data(contentsOf: url)
        return data.base64EncodedString()
    }
}

確認用 HTML

font-family に "YuGothic" を指定しておく。

<!DOCTYPE html>
<html>
<head>
  <style>
  body {
    font-size: 80px;
    font-family: YuGothic, sans-serif;
  }
  </style>
</head>
<body>
  <p>あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモーリオ市、郊外のぎらぎらひかる草の波。</p>
</body>
</html>

結果

できた・・・?

できていない気がする

  • 上記のコードを改良して、フォントデータをエンコードするタイミングなどいろいろ試してみたが、データが巨大だからか、どうしても反映されるのにラグが発生する模様。(Swift or JavaScript の処理能力の問題か?)
  • もともと、日本語フォントをウェブフォントで提供するとロード時間が気になるのでなんとかアプリ内のフォントが使えないかという動機だったのだが、どっちが速いのかはもっと検証が必要そう。
  • 無理やり感がすごい。

参考


  1. アプリにフォントを組み込むときはライセンスの確認を。