こんにちは。ハコベルカーゴの開発を担当している貞元です。
ハコベルカーゴでは、ドライバー向けのiOS・Androidアプリがあり、どちらも主にWebViewを使用しています。 iOSアプリではUIWebViewを使用していたのですが、こちらは非推奨となり更新できなくなるためため、WKWebViewへ移行した内容を紹介します。 2020年11月時点では、UIWebViewを使用したアップデート期限は2020年末以降に延長されているため、正式な期限はAppleのニュースをご確認ください。 なお、iOSアプリ開発の経験は豊富ではないため、定番と異なる点などあるかもしれません。ご了承ください。
環境
- Xcode 12.0.1
- Swift 4
対応内容
1. WebKit.frameworkを追加
WKWebViewはWebKit.frameworkに含まれているため、WebKit.frameworkを追加します。
対象のTARGETSを選択し、「General」→「Frameworks, Libraries, and Embedded Content」の「+」をクリックし、「WebKit.framework」を追加
2. StoryBoardのUIWebViewをWKWebKitへ置き換え
既存のUIWebViewはStoryBoardに配置し使用していました。 そのため、WKWebViewも同じくStoryBoardへ配置し使用します。 なお、WKWebViewをiOS8から使用できますが、StoryBoardを使用する場合はiOS11以上でないとbuildできないため、iOS Development TargetをiOS 11.0以上へ変更する必要があります。
UIWebViewをWKWebViewへ置き換え
NOTE: dataDetectorTypesについて
WKWebViewにはUIWebViewと同様にdataDetectorTypesが存在します。
ただ、StoryBoardを使用した場合はコード上で変更しても適用されないため、こちらで設定が必要です。
3. Delegateを書き換え
WKWebViewには、WKUIDelegate, WKUIDelegateの2種類のDelegateが存在します。 UIWebViewのUIWebViewDelegateはWKNavigationDelegateへ、JavaScriptでalert, confirm, promptを使用している場合はWKUIDelegateが必要です。
WebKitをインポート
+ import WebKit
Delegateを書き換え
- class WebViewController: UIViewController, UITextViewDelegate, UIWebViewDelegate { + class WebViewController: UIViewController, UITextViewDelegate, WKNavigationDelegate, WKUIDelegate {
- @IBOutlet weak var webview: UIWebView! + @IBOutlet weak var webview: WKWebView! override func viewDidLoad() { super.viewDidLoad() - webview.delegate = self + webview.navigationDelegate = self + webview.uiDelegate = self
UIWebViewDelegate→WKUIDelegate
- func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool{ + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
- func webViewDidStartLoad(_ webView: UIWebView){ + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
- func webView(_ webView: UIWebView, didFailLoadWithError error: Error){ + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
- func webViewDidFinishLoad(_ webView: UIWebView){ + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
NOTE: カスタムURLスキームについて
telやmail、その他カスタムURLスキームはそのままでは動作しません。 そのため、decidePolicyForにて処理する必要があります。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let request = navigationAction.request let url = navigationAction.request.url if url!.absoluteString.hasPrefix("http://") || url!.absoluteString.hasPrefix("https://") { switch navigationAction.navigationType { case .linkActivated: if navigationAction.targetFrame == nil || !navigationAction.targetFrame!.isMainFrame { UIApplication.shared.open(url!, options: [:], completionHandler: nil) decisionHandler(.cancel) return } case .backForward: break case .formResubmitted: break case .formSubmitted: break case .other: break case .reload: break } } else { if url!.absoluteString.range(of: "//itunes.apple.com/") != nil { if UIApplication.shared.responds(to: #selector(UIApplication.open(_:options:completionHandler:))) { UIApplication.shared.open(url!, options: [UIApplicationOpenURLOptionUniversalLinksOnly:false], completionHandler: { (finished: Bool) in }) } else { UIApplication.shared.open(url!, options: [:], completionHandler: nil) } } else { if UIApplication.shared.canOpenURL(url!) { UIApplication.shared.open(url!, options: [:], completionHandler: nil) decisionHandler(.cancel) return } } decisionHandler(.cancel) return } decisionHandler(.allow) }
WKUIDelegate
/** JavaScriptのalertを表示 */ func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert) let otherAction = UIAlertAction(title: "OK", style: .default) { action in completionHandler() } alertController.addAction(otherAction) present(alertController, animated: true, completion: nil) } /** JavaScriptのconfirmを表示 */ func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in completionHandler(false) } let okAction = UIAlertAction(title: "OK", style: .default) { action in completionHandler(true) } alertController.addAction(cancelAction) alertController.addAction(okAction) present(alertController, animated: true, completion: nil) } /** JavaScriptのpromptを表示 */ func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alertController = UIAlertController(title: "", message: prompt, preferredStyle: .alert) let okHandler: () -> Void = { if let textField = alertController.textFields?.first { completionHandler(textField.text) } else { completionHandler("") } } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in completionHandler(nil) } let okAction = UIAlertAction(title: "OK", style: .default) { action in okHandler() } alertController.addTextField { $0.text = defaultText } alertController.addAction(cancelAction) alertController.addAction(okAction) present(alertController, animated: true, completion: nil) }
4. JavaScript呼び出しの変更
UIWebViewにてJavaScriptを呼び出しにstringByEvaluatingJavaScriptを使用していました。 WKWebViewではevaluateJavaScriptを使用します。 こちらは非同期の実行となるため、挙動が変わらないように同期実行用のメソッドを用意し、そちらへ置き換えを行いました。
func evaluateJavaScriptSync(webview:WKWebView, script:String) -> Any? { var syncResult:Any? = "" var jsCompleted = false webview.evaluateJavaScript(script) { (result, error) in syncResult = result jsCompleted = true } while !jsCompleted { RunLoop.current.run(mode: .defaultRunLoopMode, before: Date() + 0.1) } return syncResult }
5. Cookie参照の変更
UIWebViewではCookieの参照にHTTPCookieStorage.sharedを使用していました。 しかし、WKWevViewではHTTPCookieStorage.sharedへ反映されず参照できないため、WKWevViewから参照するように変更しています。
webview.configuration.websiteDataStore.httpCookieStore.getAllCookies() {(cookies) in for cookie in cookies { // ここでCookieを参照 } }
まとめ
WKWebViewへの変更期限が近づいています。 iOSアプリ開発の経験は豊富ではないため、調査・変更・テストを繰り返して対応しました。 この記事がWKWebViewの移行の手助けとなると幸いです。