18
16

More than 1 year has passed since last update.

WKWebViewでローカルのindex.htmlを読み込んでscriptを実行してみた

Last updated at Posted at 2019-06-07

ネットを介してのWebView表示ではなく、手元にあるHTMLをWebViewで表示、scriptタグ内のJSメソッドの呼び出し方法をまとめます。

SwiftでWebViewを表示させる場合、WKWebViewを使うことがほとんどだと思います。現在ではUIWebViewは非推奨です。そちらについては以下の記事がとても参考になります。
[参考元]:UIWebViewを使わない理由とWKWebViewを使う理由

環境

OS: Mojave 10.14.5
Xcode: 10.1
Swift 4.2

HTML

今回はindex.htmlをWKWebViewでロードし、<script>タグ内にあるJSメソッドを呼び出すことをします。
HTMLファイルはプロジェクト内に置きます。置き場は自由で構わないのですが、例として以下のように配置しておきます。

project
├─Resource
│ ├index.html
│ └sample.json
│
└─View
  └SomeViewController

HTMLのロード

ローカルのHTMLファイルはパスを取得する必要があり、取得にはBundle.main.pathを使用します。
仕様ではBundle.main.pathはプロジェクトのBuild Phases > Copy Bundle Resourcesにあるファイルを取得するようです。
まずこの項目を確認し、取得したいHTMLファイルがなければ追加しておきましょう。

[参考元]:Swiftでプロジェクト内のファイルを取得する際の注意点2つ

HTMLファイルのパスをURL型にキャストしてWebViewのloadFileURLの引数に与えます。これでWebViewにロードできました。

func loadLocalHTML() {
    guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
    let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
    webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}

Swift側からscript内のjsを実行

実行タイミング

<script>タグにあるメソッドは、HTML全てがロードし終わってから呼び出さないとメソッド呼び出しに失敗します。
ロードが終わったことを通知するdelegateがWKNavigationDelegateにあります。

delegateの通知先は自分であることの宣言を忘れずに。

SomeViewContoller
webView.navigationDelegate = self
SomeViewController

extension SomeViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // ここでJSの実行をする
    }
}

JSの実行

evaluateJavaScriptを使います。引数のjavaScriptStringに、実行するメソッドをStringで渡します。
また、ここのStringではjsの文法で書く必要があります。jsの知見が乏しい筆者はここで躓きました。

[公式リファレンス]:evaluateJavaScript(_:completionHandler:)

<script>の中身は以下のようなものと仮定して、initHTML関数に引数を渡し実行する方法を解説します。

<script>
    function initHTML(id, jsonData, onload) {
        console.log('initHTMLだお');
        console.log(id, jsonData);
        onload();
    }
</script>

引数

Swiftは静的型付け、JSは動的型付けなので、ゆるい方向への値渡しです。
initHTMLの引数(id, jsonData, onload)は、Swift側ではそれぞれInt,Json,関数として渡します。

基本は文字列リテラル内での値の展開\()を使います。
Jsonに関しては、Jsonファイルを読み取ってData->Stringに変換して、値展開で渡しています。Stringで渡してもWebView側でJsonとして扱ってくれます。
関数はjsの書き方であるアロー関数を用いて書きましょう。

extension SomeViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let intValue = 10
        guard let json = getLocalJson() else { return }
        guard let strJson = String(data: json, encoding: .utf8) else { return }
        let script = "initHTML(\(intValue), \(strJson), () => { console.log('Swiftから渡された値だお') });"
        webView.evaluateJavaScript(script) { object, error in
            print(error ?? "成功")
        }
    }

    func getLocalJson() -> Data? {
        guard let path = Bundle.main.path(forResource: "sample", ofType: "json") else { return nil }
        let url = URL(fileURLWithPath: path)
        let json: Data?

        do { json = try Data(contentsOf: url) }
        catch { json = nil }

        return json
    }
}

コンソールの確認

JSの実行ができたらコンソールを確認しましょう。
しかしWebViewで実行したJSコンソールログはXcodeでは見れません。
ブラウザであるSafariで見ることができます。開発 -> 端末名 -> index.jsと選択するとWebインスペクターが開きます。

webinspector.png

呼び出しがうまくいってログが出力されています!
表示がない場合は左上らへんにある更新マークを押しましょう。

次回

WebViewのJS内でprojectのソース(画像とか)にアクセスしたときに発生するエラー、Orijin null is not byAccess-Control-Allow-Origin編に続く...

WWDC19あとがき

SwiftUIめっちゃすごい。iOSのUI回りに激震が走りましたね。
ここまでUIの作成ハードルが下がると我々iOSエンジニアの価値が相対的に下がっていくのでは…
Combine frameworkの登場により、Rxライブラリは今後必要なくなるのかな。ただObserverパターンの考え方は必要なのでここはそれなりにハードルは依然高そう。

参考

iOS WKWebView ネイティブとローカルJavascript連携

18
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
16