iOS8対策、アプリ内のhtmlファイルをWKWebViewで表示

アプリの中にhtmlファイルを持ち、それをWKWebViewで表示する方法です。iOS8向けです。iOS8のWKWebViewではhtmlファイルがロード可能なところにファイルがないと読み込めないので、その場所にコピーする手間が生じます。iOS9ではその必要はありません。ここで想定している規模は、ベースとなるhtmlの他に画像ファイルが何点かある場合です。向いているのは、文章と画像が中心のもので、ブラウザにレイアウトを任せたいものです。例えばアプリの使い方の説明などがあるでしょう。

関連するファイルのかたまりを管理するstruct

これは、ひとつのページに関連するファイルをまとめて扱うためのstructです。この記事はひとつのページに画像が数点あるようなものを想定しています。
struct FileList {
    let baseHTML: String
    let includedFiles: [String]
}
baseHTMLは拡張子が.htmlのファイルです。
includedFilesはそこに含まれる画像ファイルなどの配列です。拡張子も含めます。

ViewController

WKWebViewインスタンスを作成し、viewにWKWebViewインスタンスを貼り付けます。
上で定義したFileListのインスタンスを作り、load(fileList: , tempName: )を呼びます。
load(fileList: , tempName: )は後で説明します。
class ViewController: UIViewController {

    let webView = WKWebView()

    override func viewDidLoad() {
        super.viewDidLoad()

        webView.frame = self.view.frame
        self.view.addSubview(webView)

        let fileList = FileList(
            baseHTML: "test.html",
            includedFiles: ["Image.png"]
        )
        load(fileList: fileList, tempName: "help")
    }
ここに書かれているものは、ご自身のアプリの仕様にあわせていろいろ変更することになると思います。

load(fileList: , tempName: )でページを読み込みます。

一時的なフォルダを作成するので、そのフォルダの名前を第二引数で与えます。
fileListにあるファイルを全てコピーするようにしました。コピー先にすでに同名のものがあるときは先にそれを削除します。
このメソッドの中にfunc transferFile(fileName: String) -> URL?という関数があります。これはファイルの転送を行います。
func load(fileList: FileList, tempName: String) {

    //一時フォルダの取得
    let fileManager = FileManager.default
    let temporaryDirectoryPath = NSTemporaryDirectory()
    let temporaryDirectoryURL = URL(fileURLWithPath: temporaryDirectoryPath)
    let targetDirectoryURL: URL = temporaryDirectoryURL.appendingPathComponent(tempName)
    do {
        //withIntermediateDirectories=trueは間のディレクトリも自動で作る
        try fileManager.createDirectory(at: targetDirectoryURL, withIntermediateDirectories: true, attributes: nil)
    } catch {
        print("failed to create directory.")
    }

    func transferFile(fileName: String) -> URL? {

        var fileNames = fileName.components(separatedBy: ".")
        let name = fileNames[0]
        let ext = fileNames[1]

        if let soueceUrl = Bundle.main.url(forResource: name, withExtension: ext) {
            let targetUrl = targetDirectoryURL.appendingPathComponent(fileName)
            do {
                //これをやらないとコピーに失敗する
                if fileManager.fileExists(atPath: targetUrl.relativePath) {
                    try fileManager.removeItem(at: targetUrl)
                }
            } catch {
                print("failed to remove item.")
            }
            do {
                try fileManager.copyItem(at: soueceUrl, to: targetUrl)
                return targetUrl
            } catch {
                print("failed to copy item.")
            }
        }
        return nil
    }

    //baseファイルの移動
    if let loadUrl = transferFile(fileName: fileList.baseHTML) {

        var success = true
        for fileName in fileList.includedFiles {
            //残りのファイルの移動
            if transferFile(fileName: fileName) == nil {
                print("A loadedFile returns nil.")
                success = false
            }
        }

        if success {
            //読み込み
            let request = URLRequest(url: loadUrl)
            webView.load(request)
        }
    } else {
        print("A loadedFile returns nil.")
    }
}
コピー先にあっても全てのファイルをコピーとした理由(無駄なコピーを避けない理由)は、この手のhtmlファイルは見られる回数が、少数という前提からです。また、全てのファイルをコピーするのでアプリのバージョンアップで文章や画像を若干変更した場合にも対応できます。
ただ、あまりにファイル数が多い場合は時間がかかるかもしれません。このように、読み込みに時間がかかる、また、アプリ使用時に頻繁にロードされる、などの場合、別の方法を検討することがあると思います。

htmlファイル

htmlファイルと画像ファイルは同じ場所に置くようにしたので、html内の書き方はこうなります。
<img src = "./Image.png">

ローカライズ

ローカライズは各ファイルをXcode上でローカライズする方法にしました。
この他に、ソースコード上で分岐するやり方もよいと思います。

参考

http://yamaimo.hatenablog.jp/entry/2015/09/08/200000

さいごに

まずい点があったら指摘して下さい。

コメント

このブログの人気の投稿

Swiftのコンパイルエラー寄せ集め

コンパイルエラー覚え書き(Objective-C)

AVAudioSession細かいことまとめ(late 2014)