TL;DR
表題のような要求を実現するために、
URLSessionでHTML文字列を取得し WKWebView.loadHTMLString(_:baseURL:)
によってロードする、
という内容です。
特に目新しくもなく高等テクニックでもないのですが、案外気づいていない人も多いような気がして、記事にしてみました。
実現したいこと
(業務要件として)親ViewController側でサーバーレスポンスを事前チェックし、
- 正常の場合、子のWebView画面に遷移してコンテンツを表示
- エラーの場合、親ViewControllerに止まり何らかのエラー処理(例えばAlertを表示)
実現手法
環境
- Xcode 11.0
- Swift 5.1
Storyboard
コード
画面間のパラメータとしてHTML文字列とURLが必要になります。
(String, URL)
のタプルにtypealias
によって型の別名を与えておくとリーダブルになるかと思います。
(以下のコード例ではWebViewSource
というtypealias
を付けています)
FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
// Requestボタンtap
@IBAction func request(_ sender: Any) {
// Good Request
let urlOK = "https://qiita.com/y-some/items/f6bf2106597cde9f1969"
guard let url = URL(string: urlOK) else {
return
}
// Bad Request(404)
// let urlNG = "https://qiita.com/y-some/items/XXXXXXXXXXXXXXXXXXXX"
// guard let url = URL(string: urlNG) else {
// return
// }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// エラーハンドリング
if let error = error {
self.popErrorAlert(with: error.localizedDescription)
return
}
guard let response = response as? HTTPURLResponse else {
self.popErrorAlert(with: "bad response")
return
}
if response.statusCode != 200 {
self.popErrorAlert(with: "statusCode: \(response.statusCode)")
return
}
guard let data = data, let url = response.url else {
self.popErrorAlert(with: "bad data")
return
}
// 正常系:2画面目(WebView)に遷移する。senderとしてHTML文字列とbaseURLを渡す
let htmlString = String(data: data, encoding: .utf8) ?? ""
let webViewSource = WebViewSource(htmlString: htmlString, baseURL: url)
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showWebView", sender: webViewSource)
}
}.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 2画面目(WebView)にパラメータとしてHTML文字列とbaseURLを渡す
guard
let webViewController = segue.destination as? WebViewController,
let webViewSource = sender as? WebViewSource else {
return
}
webViewController.webViewSource = webViewSource
}
func popErrorAlert(with message: String) {
DispatchQueue.main.async {
let alertController = UIAlertController(title:"Error", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
}
WebViewController.swift
import UIKit
import WebKit
typealias WebViewSource = (htmlString: String, baseURL: URL)
class WebViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
var webViewSource: WebViewSource?
override func viewDidLoad() {
super.viewDidLoad()
guard let webViewSource = webViewSource else {
preconditionFailure("webViewSource is nil")
}
// メインコンテンツはパラメータにて受け取ったHTML文字列、CSSやJSなどのリソースはbaseURLによってロード
webView.loadHTMLString(webViewSource.htmlString, baseURL: webViewSource.baseURL)
}
}
補足
-
baseURL
の設定を忘れるとCSSやJavaScriptがロードされません。 -
baseURL
はリダイレクト後のURLを設定する必要がありますので、HTTPURLResponse.urlを使用します。