📱

WebViewとiOSでスムーズな画面遷移を実現する方法

2023/08/10に公開

リードエンジニアの西尾です。
最近はWebの新機能開発だけでなく、ネイティブアプリのために必要なWebの対応を行ったりしています。

OSIROアプリではこれまでSwiftなどで実装されたネイティブ部分がほぼなく、大部分がWebViewだけで実装されている、いわゆるガワアプリと呼ばれる類のもので、アプリとしての体験はあまりよくないものでした。
最近行ったバージョンアップにて脱WebViewを目指して基盤を作り、「アプリのような振る舞い」を行われるように改善しています。
https://osiro.it/news/11474

それでも画面の大部分はまだWebViewを使用しています。この記事ではWebViewを使用した部分であっても、ネイティブアプリのようなスムーズな画面遷移を実現する方法について、具体的に説明していきます。

WebViewからネイティブアプリへイベントを送信する

この脱WebViewのプロジェクトでは、対応過程でWebViewとネイティブ部分が混在するのは織り込み済みでしたので、早速連携のキモになるイベント送信の部分から取り掛かりました。イベント送信などはコネヒトさんの事例を参考にして実装を進めました。
https://tech.connehito.com/entry/2022/03/17/094422

ページ遷移の時にイベントを発生させる

iOSではNavigationControllerを使用している場合、任意のタイミングでpushアクションを行うことでページ遷移が行われます。
このような振る舞いをWebviewと連携して行う場合は画面遷移させたいタイミングでイベントを送信して、ネイティブアプリ上でpushアクションを行うようにするのが良さそうです。

aタグをクリックしたとき

WebViewの場合にJavaScriptでクリックのイベントハンドラーを検知して、aタグがクリックされたときにイベントを送信するようにして、Web側のコードを大きく修正しなくても良いようにしています。
また画面によってはイベントを発生させたくないのでdata属性をつかってイベントを発生しないようにしています。

  document.addEventListener('click', (e) => {
    const tagName = e.target.tagName.toLowerCase()
    if (tagName === 'a') {
      if (e.target.href !== "" && e.target.href !== undefined && e.target.href !== null) {
        e.preventDefault()
        const customEventType = e.target.dataset['eventType']
        let message = {
          type: customEventType || "push",
          data: {
            href: e.target.href,
          }
        }
        window.clientEventHandler(message)
      }
    } else {
      const closestAtag = e.target.closest('a')
      if (closestAtag) {
        if (closestAtag.href !== "" && closestAtag.href !== undefined && closestAtag.href !== null) {
          e.preventDefault()
          const customEventType = closestAtag.dataset['eventType']
          const message = {
            type: customEventType || "push",
            data: {
              href: closestAtag.href
            }
          }
          window.clientEventHandler(message)
        }
      }
    }
  }, false);

location.href

Webではリンクを押す以外の画面遷移で一部location.hrefなどを使って画面遷移をしている部分もあります。
例えば、投稿画面から投稿のリクエストをXHRでしたあと、リクエストが成功したら投稿一覧画面にリダイレクトする。などです。
しかし、location.hrefを使用すると、イベントが発生する前にWebView上での画面遷移が行われてしまいます。従って直接location.hrefを呼び出するのではなく、
iOSの場合はネイティブへのイベント送信、Webの場合はlocation.hrefを実行するというように、Webとアプリ間で挙動を変更する必要があります。

Web側でiOSのWebViewの場合とそれ以外の場合で異なるJSファイルを作成しておき、先述のイベントを送信するようにしておきます。
Webの場合はlocation.hrefを実行し、iOS Webviewの場合は、WebKitへイベント送信するようにしています。

Web
window.clientEventHandler = (message) => {
  if (message.data.href) {
    location.href = message.data.href
  }
}
iOS WebView
  window.clientEventHandler = (message) => {
    if (window.webkit && window.webkit.messageHandlers) {
      window.webkit.messageHandlers.actionHandler.postMessage(message)
    } else {
      console.log(`catched event:`, message)
    }
  }

このようにしておけば同じclientEventの発行を行っても、各プラットフォームで行われる振る舞いを変えることができます。

aタグをクリックしたときにすぐにNavigationBarのタイトルに反映する

アプリを完成させて社内テストをしていましたが、NavigationBarへのタイトル反映が遅い場合があるとフィードバックをいただきました。
WebViewのHTMLからtitleを取得してNavigationBarに設定する実装を行っていたのですが、それだとWebページの表示完了を待たないとタイトルが反映されない問題がありました。
動作が軽快なWebページであればまだ我慢できますが、完全に読み込みが完了しないとtitleには反映されず体験が良くないので、解決を試みました。

ページ遷移の時に発生させるイベントに、記事のタイトルをWebのaタグに対して埋め込んでおくことで対応が可能でした。
OSIROではaタグに対して data-native-title というdata属性をつけることで、イベントに記事のタイトルを添えてネイティブアプリにイベントを送信できるようにしました。

さいごに

以上のようにiOSアプリに対して、WebView側でもスムーズな体験を提供するために様々な工夫が行われています。脱WebViewと謳ってはいますが、必要に応じて使うこともあるので、引き続きWebViewとは付き合っていくんじゃないかなと思っています。
これからもネイティブアプリの体験を良くするための改善をWeb側からも引き続き行ってまいります。

OSIRO テックブログ

Discussion