SwiftでWebViewのナビゲーションをデリゲートを使って実装する方法

SwiftでWebViewを使用する場合、ユーザーがWebページをナビゲートする際に、特定の動作を制御したり、カスタマイズした動作を追加する必要がある場合があります。そのような場合に活用できるのが、デリゲートパターンを使ったナビゲーション制御です。

本記事では、デリゲートを用いてWebViewのナビゲーション処理を実装する方法について詳しく解説します。具体的には、WebViewにデリゲートを設定し、ページの読み込みやエラーハンドリングなどを細かく制御する方法を紹介します。これにより、ユーザーがWebページを操作する際に、より柔軟でカスタマイズされた体験を提供できるようになります。

目次

WebViewの基本概念

WebViewは、アプリケーション内でWebコンテンツを表示するためのコンポーネントです。iOSアプリ開発においては、WKWebViewが主に使用され、Webページのレンダリングやブラウザの機能をアプリ内で提供する役割を果たします。

WebViewの用途

WebViewは、ネイティブアプリケーションの一部として、以下のような用途に活用されます。

  • Webページの表示:アプリ内で直接Webページを表示することができ、外部ブラウザを開かずにコンテンツを提供。
  • ハイブリッドアプリの構築:HTMLやJavaScriptを使用して、Webとネイティブの機能を組み合わせたハイブリッドアプリを構築可能。
  • カスタムWebブラウザ:特定の機能に特化したカスタムブラウザをアプリ内で作成できる。

WebViewの基本的な使い方

WKWebViewを使う際には、インスタンスを作成し、指定したURLを読み込むことで簡単にWebページを表示できます。以下は基本的なコード例です。

import WebKit

class ViewController: UIViewController {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

このように、WebViewはシンプルに設定することが可能ですが、ナビゲーション制御やエラーハンドリングをカスタマイズするためには、デリゲートを利用した高度な制御が必要です。それをどのように実現するかを次に解説します。

デリゲートパターンとは

デリゲートパターンは、iOS開発において非常に一般的な設計パターンで、あるオブジェクトの特定の処理を別のオブジェクトに委任するための仕組みです。このパターンを使うことで、オブジェクト間の依存関係を最小限に抑えつつ、柔軟に動作をカスタマイズすることが可能になります。

デリゲートパターンの役割

デリゲートパターンは、主に以下のような状況で利用されます。

  • カスタムイベントの処理:イベントが発生したときに、その処理を他のオブジェクトに委譲する。
  • 動作の制御:処理の進行や結果に基づいて、動作を変更・制御することができる。
  • 再利用性の向上:元のオブジェクトを変更することなく、異なるコンテキストで動作をカスタマイズ可能。

デリゲートを用いることで、例えばWebViewのページ読み込みの開始や終了、エラー発生時の処理などを、呼び出し元で柔軟に制御することができます。

デリゲートパターンの仕組み

デリゲートパターンでは、あるオブジェクト(デリゲートを持つオブジェクト)が、特定の動作やイベントに対する処理を別のオブジェクト(デリゲート)に委任します。これを実現するために、デリゲートを持つオブジェクトはプロトコルを定義し、そのプロトコルに準拠したオブジェクトが処理を引き受けるという流れになります。

例えば、WKWebViewのデリゲートメソッドは、ナビゲーションや読み込み状態の変化に対応するメソッドを持ち、これを使ってアプリケーションの動作を細かく制御することができます。

次に、このデリゲートをWebViewにどのように設定するかを詳しく説明します。

WebViewのデリゲート設定

WKWebViewを使用してWebコンテンツを表示する場合、ナビゲーションの開始や終了、エラー処理などをカスタマイズしたい時には、デリゲートを設定することが必要です。デリゲートを設定することで、ページ遷移やエラー発生時の処理をフレキシブルに対応できるようになります。

デリゲートの設定方法

WKWebViewの場合、デリゲートを設定するためには、WKNavigationDelegateプロトコルに準拠したクラスに、WebViewのナビゲーションに関するメソッドを実装する必要があります。まず、WKWebViewを正しくセットアップし、デリゲートを設定する手順を確認します。

import WebKit

class ViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // WebViewのインスタンスを作成
        webView = WKWebView(frame: self.view.frame)
        webView.navigationDelegate = self // デリゲートの設定
        self.view.addSubview(webView)

        // 指定したURLの読み込み
        if let url = URL(string: "https://www.example.com") {
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }
}

このコードでは、ViewControllerWKNavigationDelegateプロトコルに準拠し、webView.navigationDelegate = selfによってWebViewのデリゲートに自身を設定しています。この設定によって、WebViewのナビゲーションイベントに応じて処理をカスタマイズすることが可能になります。

デリゲート設定の利点

デリゲートを設定することで、以下のような処理が可能になります。

  • ページの読み込み開始・終了の検知:Webページがどのタイミングで読み込みを開始し、完了したのかを把握できる。
  • ナビゲーションの制御:特定のURLに基づいてページ遷移を制御し、不要なリンクのブロックやリダイレクトができる。
  • エラーハンドリング:ページ読み込み中に発生したエラーを適切に処理し、ユーザーに通知できる。

次に、WebViewのナビゲーションに関する具体的なデリゲートメソッドを見ていきます。これらのメソッドを活用することで、ナビゲーションの制御を行います。

ナビゲーションに関するデリゲートメソッド

WKNavigationDelegateプロトコルを使用することで、WKWebViewのナビゲーションに関する処理を細かく制御できます。これにより、ページの読み込み状況やエラーハンドリング、ページ遷移の制御などを行うことが可能です。ここでは、ナビゲーションに関連する主要なデリゲートメソッドを紹介します。

1. ページ読み込みの開始を検知する: `webView(_:didStartProvisionalNavigation:)`

このメソッドは、Webページの読み込みが開始されたときに呼び出されます。ユーザーがリンクをクリックして別のページへ移動した際や、初めてページが読み込まれる際に、このメソッドでナビゲーションの開始を検知できます。

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print("ページの読み込みが開始されました")
}

2. ページ読み込みの完了を検知する: `webView(_:didFinish:)`

ページの読み込みが正常に完了した場合に呼び出されるメソッドです。このタイミングで、ページが正常に表示されたことを確認したり、読み込み完了後に行いたい処理を記述することができます。

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("ページの読み込みが完了しました")
}

3. 読み込みエラーの検知: `webView(_:didFailProvisionalNavigation:withError:)`

ページの読み込み中にエラーが発生した場合、このメソッドが呼び出されます。例えば、ネットワーク接続の問題やサーバーエラーなどが発生した際に、このメソッドを使用して適切なエラーハンドリングを行います。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("ページの読み込み中にエラーが発生しました: \(error.localizedDescription)")
}

4. ページ遷移を制御する: `webView(_:decidePolicyFor:decisionHandler:)`

このメソッドを使用すると、特定のリンクに対するページ遷移を許可するかどうかを制御できます。たとえば、特定のURLをフィルタリングしてブロックしたり、別の動作に置き換えることができます。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "www.example.com" {
            decisionHandler(.allow) // 指定されたURLの場合、遷移を許可
        } else {
            decisionHandler(.cancel) // 他のURLは遷移をブロック
        }
    }
}

5. リダイレクトの検知: `webView(_:didReceiveServerRedirectForProvisionalNavigation:)`

サーバーがリダイレクトを返した場合に呼び出されるメソッドです。このメソッドを使用してリダイレクトを監視し、特定の条件に基づいて追加の処理を行うことが可能です。

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    print("サーバーがリダイレクトを返しました")
}

これらのデリゲートメソッドを使うことで、WebViewのナビゲーションに対して細かい制御を行い、ユーザー体験を向上させることができます。次は、これらのメソッドを用いた具体的な実装例を紹介します。

デリゲートメソッドの実装例

ここでは、先に紹介したデリゲートメソッドを使って、実際にWKWebViewのナビゲーション処理をカスタマイズする実装例を紹介します。これにより、リンクの遷移制御やページ読み込みの状態に応じた処理ができるようになります。

1. ページ読み込みの開始と完了の処理

ページが読み込まれ始めたときにローディングインジケーターを表示し、読み込みが完了したらインジケーターを非表示にする例を見てみましょう。

import WebKit

class ViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!
    var loadingIndicator: UIActivityIndicatorView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // WebViewとインジケーターの設定
        webView = WKWebView(frame: self.view.frame)
        webView.navigationDelegate = self
        self.view.addSubview(webView)

        loadingIndicator = UIActivityIndicatorView(style: .large)
        loadingIndicator.center = self.view.center
        self.view.addSubview(loadingIndicator)

        // 指定したURLの読み込み
        if let url = URL(string: "https://www.example.com") {
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }

    // ページの読み込みが開始されたときの処理
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        loadingIndicator.startAnimating()
        print("ページの読み込みが開始されました")
    }

    // ページの読み込みが完了したときの処理
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        loadingIndicator.stopAnimating()
        print("ページの読み込みが完了しました")
    }
}

この例では、didStartProvisionalNavigationメソッドでインジケーターを表示し、didFinishメソッドで非表示にしています。これにより、ユーザーにページ読み込みの進行状況を視覚的に示すことができます。

2. 特定のリンク遷移を制御する

次に、特定のURLに基づいてページ遷移を制御する例を紹介します。例えば、外部サイトへの遷移をブロックするような処理を実装することができます。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "www.example.com" {
            // 例:example.comへの遷移を許可
            decisionHandler(.allow)
        } else {
            // 外部リンクへの遷移をブロック
            print("外部リンクへの遷移をブロックしました: \(url.host ?? "")")
            decisionHandler(.cancel)
        }
    }
}

このコードでは、decidePolicyForメソッドを使用して、リンクのホストがwww.example.comであれば遷移を許可し、それ以外のリンクはブロックするようにしています。このようにして、アプリ内で外部サイトへの遷移を制御できます。

3. エラーハンドリングの実装

ページ読み込み中にエラーが発生した場合、ユーザーに適切なフィードバックを表示することが重要です。以下は、エラー発生時にアラートを表示する例です。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    let alert = UIAlertController(title: "エラー", message: "ページの読み込みに失敗しました。", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    self.present(alert, animated: true, completion: nil)

    print("読み込みエラー: \(error.localizedDescription)")
}

この実装では、didFailProvisionalNavigationメソッドで読み込みエラーが発生した際に、アラートを表示してユーザーにエラーを通知しています。

4. 読み込み進行の監視

Webページの読み込み進行を監視し、プログレスバーなどを表示することもできます。以下の例では、estimatedProgressを使って進行状況を取得し、プログレスバーを更新します。

override func viewDidLoad() {
    super.viewDidLoad()

    webView = WKWebView(frame: self.view.frame)
    webView.navigationDelegate = self
    self.view.addSubview(webView)

    // プログレスバーの設定
    let progressBar = UIProgressView(progressViewStyle: .default)
    progressBar.frame = CGRect(x: 0, y: 80, width: self.view.frame.width, height: 20)
    self.view.addSubview(progressBar)

    webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

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

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "estimatedProgress" {
        print("読み込み進行状況: \(webView.estimatedProgress)")
    }
}

estimatedProgressを使うことで、Webページの読み込み進行度をリアルタイムで追跡し、プログレスバーを表示することができます。

これらの実装例を使うことで、WebViewに対するナビゲーションやエラーハンドリングをカスタマイズし、ユーザーにより良い体験を提供できます。次は、リンクやナビゲーションのより詳細な制御方法について解説します。

ナビゲーションの制御

WebViewを使ったアプリケーションで、特定のリンクをフィルタリングしたり、特定のページ遷移を制限することが必要な場合があります。デリゲートメソッドを活用することで、ページ遷移を柔軟に制御し、特定のリンクのみを許可したり、特定の条件でナビゲーションをブロックすることが可能です。

特定のリンクのフィルタリング

アプリ内で、特定のドメインやURLパターンに対してのみナビゲーションを許可したい場合、WKNavigationDelegatedecidePolicyForメソッドを使用してリンク遷移を制御することができます。以下の例では、特定のドメイン(例: example.com)に対してのみナビゲーションを許可し、それ以外のドメインへの遷移はブロックします。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "www.example.com" {
            // example.com への遷移を許可
            decisionHandler(.allow)
        } else {
            // その他のサイトへの遷移をブロック
            decisionHandler(.cancel)
            print("外部リンクへの遷移がブロックされました: \(url.absoluteString)")
        }
    } else {
        decisionHandler(.cancel)
    }
}

このコードは、ユーザーがwww.example.com以外のドメインにアクセスしようとした場合、その遷移をブロックし、指定されたドメインへのアクセスのみを許可します。これにより、特定のWebサイトに限定してコンテンツを提供する場合や、外部リンクを避けたい状況に対応できます。

特定のURLスキームを扱う

また、Webページ内のリンクがカスタムURLスキーム(例: tel:mailto:など)を使用している場合、そのリンクに対して特別な処理を行いたいことがあります。例えば、電話番号のリンクをタップした際に電話アプリを起動するなどです。以下の例では、tel:スキームのリンクがタップされたときに、iPhoneの電話アプリを起動するようにしています。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.scheme == "tel" {
            // 電話アプリを起動
            if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
                decisionHandler(.cancel) // WebViewでの遷移はキャンセル
                return
            }
        }
    }
    decisionHandler(.allow)
}

このコードでは、tel:スキームを検出した際に、iPhoneの電話アプリを起動し、WebView内でのナビゲーションはキャンセルしています。同様に、mailto:スキームを使ってメールアプリを開くことも可能です。

リダイレクトの処理

リダイレクトが発生した際、その処理を適切に制御することも重要です。WKWebViewはリダイレクトを自動的に処理しますが、必要に応じてこれを監視し、特定のリダイレクトを処理しないようにすることも可能です。以下の例では、リダイレクトが発生したときに、その内容を監視し、必要に応じて処理を行います。

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    print("サーバーリダイレクトが発生しました")
    // リダイレクト先に応じて処理をカスタマイズ可能
}

このメソッドでは、サーバー側からリダイレクトが発生した際に、それを検知して処理を追加できます。たとえば、特定のリダイレクト先に対して通知を表示したり、さらに詳細なナビゲーション制御を行うことができます。

ナビゲーションのキャンセル

ナビゲーションを完全にブロックする場合、decidePolicyForメソッド内でdecisionHandler(.cancel)を呼び出すことでナビゲーションをキャンセルできます。たとえば、特定の条件が満たされていない場合にナビゲーションをブロックする際に使います。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    // 特定の条件を満たしていない場合、ナビゲーションをキャンセル
    if navigationAction.request.url?.path == "/restricted" {
        print("アクセスが制限されています")
        decisionHandler(.cancel)
        return
    }
    decisionHandler(.allow)
}

この例では、/restrictedというパスへのアクセスを制限し、それ以外のナビゲーションのみ許可しています。このように、WebViewのナビゲーション制御を細かく設定することで、アプリケーションの要件に応じたカスタマイズが可能です。

次は、エラーハンドリングの実装について詳しく説明します。エラーが発生した際の適切な処理により、ユーザー体験を向上させることができます。

WebViewのエラーハンドリング

WebViewでWebページを表示する際、ネットワークの問題やサーバーのエラー、ページの存在しないURLにアクセスするなど、さまざまなエラーが発生する可能性があります。これらのエラーに対処し、ユーザーに適切なフィードバックを提供するためには、エラーハンドリングが重要です。

WKNavigationDelegateを利用すると、ページ読み込み時のエラーを検知し、エラーの種類に応じた処理を行うことができます。ここでは、WebViewのエラーハンドリング方法について具体的な実装例を紹介します。

エラーハンドリングの基本メソッド

WKNavigationDelegateには、読み込みエラーが発生した際に呼び出されるメソッドがいくつかあります。これらを利用して、エラーの種類ごとに異なる対応を行うことができます。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("ページの読み込み中にエラーが発生しました: \(error.localizedDescription)")
}

このメソッドは、ページの読み込みが開始された直後にエラーが発生した場合に呼び出されます。たとえば、URLが無効であったり、ネットワーク接続が不安定な場合に適用されます。

エラーハンドリングの実装例

ここでは、ネットワークエラーやページが見つからなかった場合などに対処する具体的な実装例を見ていきます。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    if let nsError = error as NSError? {
        switch nsError.code {
        case NSURLErrorNotConnectedToInternet:
            // インターネット接続エラー
            showAlert(title: "接続エラー", message: "インターネットに接続されていません。")
        case NSURLErrorTimedOut:
            // タイムアウトエラー
            showAlert(title: "タイムアウト", message: "ページの読み込みがタイムアウトしました。")
        case NSURLErrorCannotFindHost:
            // ホストが見つからないエラー
            showAlert(title: "エラー", message: "指定されたページが見つかりません。")
        default:
            // その他のエラー
            showAlert(title: "エラー", message: "ページの読み込みに失敗しました。")
        }
    }
}

func showAlert(title: String, message: String) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    self.present(alert, animated: true, completion: nil)
}

この例では、エラーコードに基づいて異なるエラーメッセージを表示しています。具体的には、インターネット接続の問題、タイムアウトエラー、指定したページが存在しない場合など、エラーの種類に応じたアラートを表示するようにしています。

エラーページのカスタマイズ

エラーメッセージを表示するだけでなく、カスタムエラーページを表示することもできます。たとえば、ネットワークエラーが発生した場合に、標準的なアラートではなく、特定のHTMLコンテンツを表示して、ユーザーに再接続を促すようなメッセージを提供することが可能です。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    let errorHTML = """
    <html>
    <body>
    <h1>ページを表示できません</h1>
    <p>インターネット接続に問題があります。再接続してください。</p>
    </body>
    </html>
    """
    webView.loadHTMLString(errorHTML, baseURL: nil)
}

このコードでは、エラーが発生した際にカスタムHTMLをWebViewに表示することで、エラーページを提供しています。この方法を使うと、ユーザーに視覚的なフィードバックを提供し、より直感的にエラーの状況を伝えることができます。

接続がタイムアウトした場合の対処

Webページの読み込みがタイムアウトする場合、ユーザーに対して再試行のオプションを提供することが考えられます。例えば、読み込みがタイムアウトしたときに再試行ボタンを表示し、ユーザーが手動でページの再読み込みを行うことができるようにします。

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    if (error as NSError).code == NSURLErrorTimedOut {
        let retryHTML = """
        <html>
        <body>
        <h1>タイムアウトしました</h1>
        <p>ページの読み込みに失敗しました。<a href="javascript:window.location.reload()">再試行</a></p>
        </body>
        </html>
        """
        webView.loadHTMLString(retryHTML, baseURL: nil)
    }
}

この例では、読み込みがタイムアウトした場合にカスタムエラーページを表示し、ページの再試行を行えるリンクを提供しています。これにより、ユーザーは簡単に再接続を試みることができます。

ユーザー体験を向上させるためのエラーハンドリングのポイント

エラーハンドリングを実装する際、ユーザー体験を向上させるために次のポイントに留意しましょう。

  1. わかりやすいエラーメッセージを表示し、ユーザーが何が問題であるかを理解できるようにする。
  2. 再試行のオプションを提供し、ユーザーが簡単に操作できるようにする。
  3. カスタムエラーページを利用して、エラー発生時でもアプリのデザインやユーザー体験を保つ。
  4. エラーのログ出力を行い、問題が発生した場合に開発者が簡単にトラブルシューティングできるようにする。

これらの実装を通じて、WebViewのエラー発生時にもスムーズで一貫したユーザー体験を提供することができます。次は、ページ読み込み状況の監視方法について解説します。

ページの読み込み状況の監視

WebViewを利用する際、ページの読み込み進行状況をリアルタイムで監視することは、ユーザー体験を向上させるために重要です。ページの読み込みがどの程度進んでいるのかを示すことで、ユーザーに安心感を与えたり、適切なフィードバックを提供できます。WKWebViewでは、ページの読み込み進捗状況を監視するために、estimatedProgressというプロパティを利用します。

このプロパティを活用して、進行状況をプログレスバーなどに反映させることが可能です。ここでは、読み込み状況を監視し、プログレスバーに反映させる方法について解説します。

読み込み状況の監視方法

WKWebViewestimatedProgressプロパティは、ページの読み込みが進むにつれて0.0から1.0までの値を取ります。このプロパティをKVO(Key-Value Observing)を用いて監視し、進行状況を追跡します。

以下は、プログレスバーを使ってページの読み込み進行状況を表示する実装例です。

import WebKit

class ViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!
    var progressView: UIProgressView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // WebViewの設定
        webView = WKWebView(frame: self.view.frame)
        webView.navigationDelegate = self
        self.view.addSubview(webView)

        // プログレスバーの設定
        progressView = UIProgressView(progressViewStyle: .default)
        progressView.frame = CGRect(x: 0, y: 80, width: self.view.frame.width, height: 20)
        self.view.addSubview(progressView)

        // estimatedProgressの監視を追加
        webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

        // ページの読み込み
        if let url = URL(string: "https://www.example.com") {
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }

    // estimatedProgressが変わったときの処理
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "estimatedProgress" {
            progressView.progress = Float(webView.estimatedProgress)
        }
    }

    // ページの読み込みが完了したらプログレスバーを非表示にする
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        progressView.isHidden = true
    }
}

このコードでは、以下のステップでページの読み込み状況を監視し、プログレスバーに反映しています。

  1. estimatedProgressの監視を追加: addObserverを使って、WebViewのestimatedProgressプロパティを監視。
  2. 進行状況の更新: observeValueメソッドで進行状況を取得し、プログレスバーの値を更新。
  3. 読み込み完了時の処理: ページの読み込みが完了したら、didFinishメソッドでプログレスバーを非表示にする。

これにより、Webページの読み込みが進むごとにプログレスバーが更新され、ユーザーに進捗を視覚的に知らせることができます。

リロードボタンの実装

ページの読み込みが完了するまでの進捗を表示するだけでなく、ユーザーが手動でページを再読み込みできるようにするリロードボタンを追加することもできます。以下は、リロードボタンの実装例です。

override func viewDidLoad() {
    super.viewDidLoad()

    // WebViewとプログレスバーの設定
    webView = WKWebView(frame: self.view.frame)
    webView.navigationDelegate = self
    self.view.addSubview(webView)

    progressView = UIProgressView(progressViewStyle: .default)
    progressView.frame = CGRect(x: 0, y: 80, width: self.view.frame.width, height: 20)
    self.view.addSubview(progressView)

    // リロードボタンの設定
    let reloadButton = UIButton(frame: CGRect(x: 0, y: 100, width: 100, height: 50))
    reloadButton.setTitle("リロード", for: .normal)
    reloadButton.backgroundColor = .systemBlue
    reloadButton.addTarget(self, action: #selector(reloadPage), for: .touchUpInside)
    self.view.addSubview(reloadButton)

    // estimatedProgressの監視
    webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

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

@objc func reloadPage() {
    webView.reload()
}

この実装では、リロードボタンを追加し、ユーザーが手動でページを再読み込みできるようにしています。reloadPageメソッドをボタンのタップイベントに関連付けており、ボタンを押すとWebViewが現在のページをリロードします。

ナビゲーションバーにプログレスバーを組み込む

多くのブラウザやアプリでは、画面上部にあるナビゲーションバーにプログレスバーを組み込んで表示します。以下のように、UINavigationBarにプログレスバーを組み込む方法も簡単に実装できます。

override func viewDidLoad() {
    super.viewDidLoad()

    // WebViewの設定
    webView = WKWebView(frame: self.view.frame)
    webView.navigationDelegate = self
    self.view.addSubview(webView)

    // ナビゲーションバーにプログレスバーを追加
    progressView = UIProgressView(progressViewStyle: .default)
    progressView.sizeToFit()
    let progressItem = UIBarButtonItem(customView: progressView)
    self.navigationItem.rightBarButtonItem = progressItem

    // estimatedProgressの監視
    webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

    // ページの読み込み
    if let url = URL(string: "https://www.example.com") {
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "estimatedProgress" {
        progressView.progress = Float(webView.estimatedProgress)
    }
}

このコードでは、ナビゲーションバーにプログレスバーを配置しています。これにより、画面上部でWebページの読み込み進行状況を表示することができます。

監視の解除

estimatedProgressの監視を続けると、不要なオブザーバが残ってしまいメモリリークの原因となる可能性があります。監視を解除するために、deinitメソッド内でオブザーバを削除することが重要です。

deinit {
    webView.removeObserver(self, forKeyPath: "estimatedProgress")
}

これにより、WebViewが解放される際にestimatedProgressの監視を適切に解除し、メモリリークを防ぐことができます。

次は、WebViewのデリゲートを利用した応用的なナビゲーション処理について解説します。

デリゲートの応用例

WebViewのデリゲートを活用することで、単なるページ表示にとどまらない、より高度なナビゲーションやインタラクションを実現できます。ここでは、デリゲートを利用した応用的なWebViewの使い方やナビゲーション制御の例を紹介します。

1. 特定のリンクを別のブラウザで開く

アプリ内でWebページを表示する際、特定のリンク(外部サイトやサードパーティサービス)をアプリ外のデフォルトブラウザ(Safariなど)で開きたい場合があります。これを実現するために、decidePolicyForメソッドを利用します。以下は、特定のドメインにアクセスする場合に外部ブラウザで開く実装例です。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "www.external-site.com" {
            // 外部リンクをSafariで開く
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            decisionHandler(.cancel) // WebView内での遷移をキャンセル
        } else {
            decisionHandler(.allow) // それ以外はWebView内で遷移を許可
        }
    } else {
        decisionHandler(.allow)
    }
}

この実装では、指定された外部サイト(www.external-site.com)へのアクセスがリクエストされたときに、UIApplication.shared.openを使ってSafariでそのリンクを開き、WebView内での遷移はキャンセルしています。これにより、ユーザーにとっての利便性が向上します。

2. JavaScriptとの連携

WKWebViewでは、JavaScriptを利用したWebページとの双方向通信が可能です。たとえば、Webページ側で実行されたJavaScriptからアプリのネイティブ機能を呼び出すことができます。これには、WKScriptMessageHandlerを利用します。

以下の例では、Webページから送られたJavaScriptメッセージを受け取り、アプリ側で処理を行います。

import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // WebViewの設定
        let contentController = WKUserContentController()
        contentController.add(self, name: "jsToNative")

        let config = WKWebViewConfiguration()
        config.userContentController = contentController

        webView = WKWebView(frame: self.view.frame, configuration: config)
        webView.navigationDelegate = self
        self.view.addSubview(webView)

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

    // JavaScriptからのメッセージを受け取る
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "jsToNative", let messageBody = message.body as? String {
            print("JavaScriptからのメッセージ: \(messageBody)")
            // メッセージに基づいてアプリのネイティブ処理を行う
        }
    }
}

この例では、JavaScript側からwindow.webkit.messageHandlers.jsToNative.postMessage("メッセージ")のように呼び出すことで、アプリのネイティブコードにメッセージを送信できます。これにより、Webコンテンツとアプリの機能を密に連携させることが可能になります。

3. クッキーやセッション管理

Webアプリケーションを使用する場合、クッキーやセッション管理が必要になることがあります。WKWebViewでは、クッキーを適切に管理することで、ユーザーがログインしたセッションを保持したり、カスタマイズされたコンテンツを提供することができます。

以下は、WKHTTPCookieStoreを利用してクッキーを取得・設定する方法です。

// クッキーの取得
if let cookies = webView.configuration.websiteDataStore.httpCookieStore {
    cookies.getAllCookies { (cookies) in
        for cookie in cookies {
            print("Cookie name: \(cookie.name), value: \(cookie.value)")
        }
    }
}

// クッキーの設定
let cookie = HTTPCookie(properties: [
    .domain: "www.example.com",
    .path: "/",
    .name: "session_id",
    .value: "abc123",
    .secure: "TRUE",
    .expires: NSDate(timeIntervalSinceNow: 31556926)
])!

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

このコードでは、クッキーを取得してその内容をログに表示したり、新しいクッキーを作成してWKWebViewに設定することができます。これにより、Webアプリケーション内でのセッションやユーザー認証情報を適切に管理できます。

4. 動的コンテンツのロード制御

特定の条件に基づいて、動的にコンテンツをロードすることも可能です。たとえば、ユーザーのアクションやアプリの状態に応じて、Webページ内の特定のセクションを表示したり非表示にしたりする場合があります。これを実現するために、JavaScriptを使ってWebコンテンツを制御できます。

func injectJavaScript() {
    let jsCode = """
    document.getElementById('dynamic-content').style.display = 'none';
    """
    webView.evaluateJavaScript(jsCode) { (result, error) in
        if let error = error {
            print("JavaScriptの実行中にエラーが発生しました: \(error.localizedDescription)")
        } else {
            print("JavaScriptが正常に実行されました")
        }
    }
}

この例では、Webページ内の要素(ここではid="dynamic-content"の要素)をJavaScriptを用いて非表示にしています。ユーザーのアクションやアプリの状況に応じて、このような動的な制御を行うことができます。

5. Webページのコンテンツスクリーンショットの取得

WebViewの表示内容をスクリーンショットとして保存したい場合もあります。以下のコードでは、takeSnapshotメソッドを使用して、WebViewのスクリーンショットを取得します。

func takeWebViewSnapshot() {
    let config = WKSnapshotConfiguration()
    webView.takeSnapshot(with: config) { image, error in
        if let image = image {
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
            print("スクリーンショットが保存されました")
        } else if let error = error {
            print("スクリーンショットの取得中にエラーが発生しました: \(error.localizedDescription)")
        }
    }
}

このメソッドは、WebViewの現在表示しているコンテンツのスクリーンショットを取得し、それをフォトライブラリに保存します。これを使って、動的なコンテンツのキャプチャやデバッグ時の画面確認などが可能です。

まとめ

これらの応用例を活用することで、WKWebViewのデリゲートを通じた柔軟で強力なナビゲーション制御が実現できます。JavaScriptとの連携やクッキー管理、外部ブラウザでのリンクオープンなど、WebViewの可能性を広げることで、よりインタラクティブでユーザーにとって便利なアプリケーションを構築することができます。次に、デリゲートを使ったナビゲーション処理のテストとデバッグ方法について詳しく説明します。

テストとデバッグ

WKWebViewのデリゲートを使用したナビゲーション処理の実装が完成したら、適切に動作するかをテスト・デバッグすることが非常に重要です。特に、外部リンクの制御やエラーハンドリング、JavaScriptとの連携など、複雑な処理を行う場合、しっかりと動作を確認しておくことで、予期せぬ動作やエラーを未然に防ぐことができます。

ここでは、WKWebViewのデリゲートを使用したナビゲーション処理をテスト・デバッグするための手法やポイントを紹介します。

1. デリゲートメソッドの検証

デリゲートメソッドが期待通りに動作するかどうかを確認するために、まずはそれぞれのデリゲートメソッドが呼ばれているかどうかをチェックします。デリゲートメソッドの中にprint文を入れて、メソッドが正しく実行されているかどうかを確認することが簡単な手法です。

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print("ページ読み込みが開始されました")
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("ページ読み込みが完了しました")
}

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("ページ読み込み中にエラーが発生しました: \(error.localizedDescription)")
}

これにより、どのメソッドがどのタイミングで呼び出されているのか、またエラーが発生した際に正しい処理が行われているかをログで確認できます。デリゲートメソッドの動作が期待通りかどうかを検証する際の第一歩です。

2. Xcodeのデバッガを使ったブレークポイント

print文によるログ出力だけでなく、Xcodeのデバッガを活用してブレークポイントを設定することで、コードのどの箇所が実行されているかを詳細に確認することができます。ブレークポイントを使えば、メソッド内で変数の状態やURLの内容、エラーの詳細など、実行時の情報を把握することが可能です。

  1. デリゲートメソッド内にブレークポイントを設定。
  2. 実行中にデバッガの変数情報を確認。
  3. 不正な挙動があれば、その原因を絞り込む。

これにより、特定の条件で正しく動作しない問題を効率的に特定できます。

3. ネットワーク接続のシミュレーション

ナビゲーションのエラーハンドリングをテストするために、意図的にネットワーク接続を切断したり、通信速度を低下させてテストを行うことが効果的です。Xcodeの「デバイスとシミュレーター」ウィンドウでは、ネットワーク環境をシミュレーションできます。

  1. Xcodeでシミュレーターを実行。
  2. シミュレーターのメニューで「デバッグ」→「遅い3G」や「無接続」を選択。
  3. ページの読み込みが失敗した際に、アプリがどのように反応するかを確認。

これにより、エラーが発生したときの処理やユーザーへの通知が正しく行われているかをテストできます。

4. リダイレクトや外部リンクのテスト

リダイレクトや外部リンクの処理が正しく行われているかを確認するために、テスト用のWebページを使用して、特定のURLでリダイレクトや外部サイトへの遷移が正しく動作するかをチェックします。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        print("ナビゲーションリクエスト: \(url.absoluteString)")
        // 外部リンクやリダイレクトのチェック
    }
    decisionHandler(.allow)
}

上記のコードでリダイレクト先のURLや外部リンクのURLを確認し、それらに基づく処理が正しく行われているかをテストします。

5. 自動化テストの導入

UIテストを自動化することで、ナビゲーション処理が期待通りに動作するかを定期的にチェックすることができます。XcodeにはUIテストフレームワークが組み込まれており、シナリオに沿ったナビゲーションの自動テストを記述できます。以下は、簡単なUIテストの例です。

func testWebViewNavigation() {
    let app = XCUIApplication()
    app.launch()

    let webView = app.webViews.firstMatch
    XCTAssertTrue(webView.exists)

    webView.buttons["リンクボタン"].tap()
    XCTAssertTrue(app.staticTexts["次のページ"].exists)
}

このテストでは、WebView内のリンクが正しく動作し、次のページが表示されるかを確認しています。自動テストを導入することで、手動テストを効率化し、バグの早期発見に役立てることができます。

6. JavaScriptとの連携テスト

WebViewでJavaScriptと連携する場合、JavaScriptから送られるメッセージやその応答が正しく処理されているかを確認する必要があります。JavaScriptからのメッセージを受け取ったときに、期待通りの動作が行われているかをテストします。

func webView(_ webView: WKWebView, didReceive message: WKScriptMessage) {
    if message.name == "jsToNative" {
        print("JavaScriptからのメッセージ: \(message.body)")
        XCTAssertEqual(message.body as? String, "expectedValue")
    }
}

このように、JavaScriptからのメッセージ内容を検証するテストを実行し、アプリ側で適切に処理されているかを確認します。

まとめ

デリゲートを利用したナビゲーション処理は、複数の要素が関わるため、テストとデバッグが非常に重要です。Xcodeのデバッガやネットワークシミュレーション、UIテストフレームワークを駆使して、ナビゲーションのすべてのシナリオを網羅的にテストしましょう。これにより、WebViewを使ったアプリの安定性を高め、予期せぬエラーや動作不良を防ぐことができます。

まとめ

本記事では、SwiftでWKWebViewを使い、デリゲートを活用したナビゲーション処理の実装方法について詳しく解説しました。基本的なWebViewの設定から、ページ読み込みの進行管理、特定のリンクの制御、エラーハンドリング、そしてJavaScriptとの連携まで幅広い内容を取り扱いました。さらに、テストやデバッグの手法を活用することで、より安定した動作を実現し、ユーザーに快適なWeb体験を提供することが可能です。

WebViewのデリゲートを上手に使うことで、アプリの柔軟性や拡張性を高めることができるので、ぜひ実際のプロジェクトで活用してみてください。

コメント

コメントする

目次