はじめに (対象読者・この記事でわかること)

この記事は、iOSアプリ開発者、特にSwiftを使用してWebViewを実装している方を対象にしています。また、WKWebViewのタイムアウト設定に悩んでいる開発者にも役立つ内容です。

この記事を読むことで、WKWebViewのタイムアウトを正しく設定する方法、ネットワーク遅延時の対応策、そして実際のコード例を通じて実装できるようになります。特に、ロード時間が長いページや不安定なネットワーク環境でのアプリ体験を向上させるための具体的な手法を学べます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Swiftの基本的な知識 - iOS開発の基礎(StoryboardやコードベースでのUI実装経験) - WKWebViewの基本的な使い方

WKWebViewとタイムアウト設定の概要

WKWebViewはiOSアプリでWebコンテンツを表示するための強力なツールですが、デフォルト設定ではタイムアウトが明確に定義されていません。ネットワーク遅延やサーバー応答の遅延が発生した場合、ユーザーは永遠にロード画面を見続けることになります。

タイムアウト設定は、この問題を解決するために不可欠です。適切なタイムアウトを設定することで、ユーザーに待ち時間を知らせ、アプリの応答性を向上させることができます。また、タイムアウト後にエラーメッセージを表示することで、ユーザー体験を改善できます。

具体的なタイムアウト設定の実装方法

ステップ1:WKWebViewの基本的な設定

まず、WKWebViewをアプリに組み込む基本的な設定から始めましょう。ViewControllerにWKWebViewを追加し、基本的なデリゲートを設定します。

Swift
import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() // WKWebViewの初期化 let webConfiguration = WKWebViewConfiguration() webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self // WebViewをビューに追加 view.addSubview(webView) // Auto Layoutの設定 webView.translatesAutoresizingMaskIntoConstraints = false webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true // Webページの読み込み if let url = URL(string: "https://example.com") { let request = URLRequest(url: url) webView.load(request) } } }

ステップ2:タイムアウト設定の実装

WKWebView自体には直接的なタイムアウト設定がありませんが、カスタムのURLローダーを実装することでタイムアウト機能を追加できます。以下にその実装例を示します。

Swift
class TimeoutWebView: WKWebView { private var timeoutTimer: Timer? private var timeoutInterval: TimeInterval = 30 // デフォルト30秒 func load(_ request: URLRequest, timeout: TimeInterval = 30) { self.timeoutInterval = timeout startTimeoutTimer() super.load(request) } private func startTimeoutTimer() { timeoutTimer?.invalidate() timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeoutInterval, repeats: false) { [weak self] _ in self?.handleTimeout() } } private func handleTimeout() { if isLoading { stopLoading() // タイムアウト時の処理(例:エラーメッセージの表示) print("タイムアウトが発生しました") } } override func load(_ request: URLRequest) { load(request, timeout: timeoutInterval) } deinit { timeoutTimer?.invalidate() } }

ステップ3:カスタムURLローダーの実装

さらに高度なタイムアウト制御が必要な場合は、カスタムのURLローダーを実装することも可能です。以下にその例を示します。

Swift
class CustomURLSchemeHandler: NSObject, WKURLSchemeHandler { func webView(_ webView: WKWebView, start task: WKURLSchemeTask) { // リクエストを処理 let request = task.request // タイムアウトタイマーの設定 let timeout = 30 // 30秒 let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in task.didFailWithError(NSError(domain: "TimeoutError", code: -1, userInfo: nil)) self?.stopTimer() } // 実際のリクエスト処理 URLSession.shared.dataTask(with: request) { data, response, error in timer.invalidate() if let error = error { task.didFailWithError(error) return } guard let response = response as? HTTPURLResponse, let mimeType = response.mimeType, let data = data else { task.didFailWithError(NSError(domain: "InvalidResponse", code: -1, userInfo: nil)) return } let expectedContentLength = response.expectedContentLength > 0 ? response.expectedContentLength : data.count task.didReceive(response) task.didReceive(data) task didFinish() }.resume() } func webView(_ webView: WKWebView, stop task: WKURLSchemeTask) { // タスクを停止 task.cancel() } private var timers: [WKURLSchemeTask: Timer] = [:] private func stopTimer(for task: WKURLSchemeTask) { timers[task]?.invalidate() timers.removeValue(forKey: task) } }

ハマった点やエラー解決

問題1:タイマーが正しく機能しない

実装初期段階では、タイマーが正しく機能せず、タイムアウトが発生してもロードが停止しない問題に直面しました。これは、タイマーの参照が適切に保持されていなかったためです。

問題2:メモリリーク

タイマーを保持するプロパティが強参照になっており、ViewControllerが解放されない問題が発生しました。

問題3:タイムアウト後のエラーハンドリング

タイムアウト後にユーザーに適切なフィードバックを提供する方法が不明でした。

解決策

これらの問題を解決するために、以下の対策を取りました:

  1. タイマーの参照管理を改善し、適切に解放されるように修正しました。
  2. クロージャ内で[weak self]を使用して循環参照を防ぎました。
  3. タイムアウト発生時にユーザーに通知するUIを実装しました。
Swift
// 改善版のタイムアウト処理 private func handleTimeout() { guard isLoading else { return } stopLoading() // メインスレッドでUI更新を実行 DispatchQueue.main.async { // ここにタイムアウト時のUI処理を実装 self.showTimeoutAlert() } } private func showTimeoutAlert() { let alert = UIAlertController( title: "読み込みタイムアウト", message: "ページの読み込みに時間がかかっています。再試行しますか?", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "再試行", style: .default) { _ in self.retryLoading() }) alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel)) present(alert, animated: true) } private func retryLoading() { if let currentRequest = webView.url { webView.load(URLRequest(url: currentRequest)) } }

まとめ

本記事では、SwiftでWKWebViewのタイムアウトを設定する方法について解説しました。

  • WKWebViewには直接的なタイムアウト設定がないため、カスタム実装が必要である
  • カスタムWebViewクラスを作成することで、タイムアウト機能を追加できる
  • タイマーを使用したタイムアウト処理を実装し、適切なエラーハンドリングを行う
  • メモリ管理には注意し、循環参照を防ぐ必要がある
  • ユーザー体験を向上させるため、タイムアウト時には適切なフィードバックを提供する

この記事を通して、ネットワーク遅延時でもユーザーに適切なフィードバックを提供できるアプリケーションを開発できるようになったと思います。今後は、より高度なタイムアウト戦略や、オフライン時の対応についても記事にする予定です。

参考資料