[Swift] そう、あの頃の僕はただWKWebViewにアプリ内に保存された画像ファイルを表示させたかったんだ

2021.01.19

はじめに

CX事業本部の中安です。まいどです。

WKWebViewで画像を表示しようとするときにレンダリングされない事象が出てしまいました。

その経緯と、対策したことについて書き留めておこうかと思います。

どうなっていたか

アプリの実装内容は、HTML文字列をプログラミングによって生成して、最後にWKWebView上でそれを読み込ませることでした。

その中で画像を表示させるのですが、 その画像はサーバーからダウンロードをし、アプリのドキュメントディレクトリなどに保存して、それを使用する仕様です。 (※ここではドキュメントディレクトリと書きましたが、一時ディレクトリの場合もあります)

そして<img>タグを生成するときにこんな感じで作っていました。

private func imageTag(_ imageFileName: String) -> String {
    let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let imagePath = "\(documentDirectoryPath)/\(imageFileName)"
    let fileUrl = URL(fileURLWithPath: imagePath)
    return "<img src=\"\(fileUrl.absoluteString)\"/>"
}

これを使って

<!doctype html>
<html lang="ja">
:
<body>
:
<div>
    <img src="file:///var/mobile/..(中略)../Documents/(ファイル名).png"/>
</div>
:
</body>
</html>

という感じにHTML文字列化されます。

最終的には WKWebView にこの文字列を読み込ませます。

let htmlString = (上記でできあがった文字列)
let baseURL = URL(fileURLWithPath: NSTemporaryDirectory())
webView.loadHTMLString(htmlString, baseURL: baseURL)

しかし、WKWebViewではこのimgタグの画像は表示されません。

実はこのアプリは古いこともあり、WEBビューは少し前までは UIWebView を使用していて、 近くそれが使用できなくなるために WKWebView にリプレースした経緯がありました。

UIWebView ではこの方法でもファイル内容を表示できていたが、 WKWebView ではおそらくはセキュリティ的な観点で通用しなくなっていたのでしょうね。

対策

ダウンロードされた画像ファイルを直接使うことはできないので、 一度その画像ファイルのデータを読み込んでBase64エンコードをするという対策方針になりました。

参考: Base64

Base64は、データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式である。MIMEによって規定されていて、7ビットのデータしか扱うことの出来ない電子メールにて広く利用されている。具体的には、A–Z, a–z, 0–9 までの62文字と、記号2つ (+, /)、さらにパディング(余った部分を詰める)のための記号として = が用いられる。この変換によって、データ量は4/3(約133%)になる。また、MIMEの基準では76文字ごとに改行コードが入るため、この分の2バイトを計算に入れるとデータ量は約137%となる。

wikipedia

参考までにBase64で画像を取り扱う場合の書式は以下のようになります。

data:image/(拡張子);base64,(エンコードされた文字列)

コロン、セミコロン、カンマなどの位置には注意です。

Base64エンコードは、Data型に変換してしまえば仕組みは用意されていますので、 下記のように元のメソッドを改修しました。

func imageTagString(_ imageFileName: String) -> String {
    let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let imagePath = "\(documentDirectoryPath)/\(imageFileName)"
    guard
        FileManager.default.fileExists(atPath: imagePath),
        let data = FileManager.default.contents(atPath: imagePath),
        let _ = UIImage(data: data)
    else {
        return ""
    }
    let attachmentType = (imagePath as NSString).pathExtension
    let base64 = data.base64EncodedString(options: .lineLength64Characters)
    return "data:image/\(attachmentType);base64,\(base64)"
}

ハイライトしている部分がBase64エンコードをして、そのURLを作成しているところです。 画像ファイルの存在確認などを挟んでいますが、やっていることはシンプルです。

注意: JPEGの場合は、attachmentTypeの部分少し工夫がいるかもです。

<!doctype html>
<html lang="ja">
:
<body>
:
<div>
    <img src="data:image/png;base64,......................."/>
</div>
:
</body>
</html>

吐き出されるHTMLはこんな感じです。

これでローカルにあった画像ファイルが表示される運びになりました。

おわりに

というわけで「あれ?」っとハマってしまったことを書き留めておきました。

どなたかのお役に立てばと、思います。

ではまた。