iOS
webView
Swift
WKWebView
swift4

iOSでWebViewを開発する際に気をつけるべき9のこと

概要

WKWebViewを使ってWebViewを開発する気をつけるべきことをメモ

対象: iOS11以降
言語: Swift4.x

1. HTTP通信

iOS9以降ではATS(App Transport Security)が導入され、HTTP経由の通信ができなくなりました
WebViewアプリにおいては、下記の設定を Info.plist に加えることで
URLSession 経由での通信には影響を与えることなく、WebView経由での通信のみATSから除外するこができます

  <key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>
  </dict>

※ 参考
必須化まで約2ヵ月半!App Transport Securityについて _ セキュリティ対策のラック

2. Cookie管理

iOS11以降は下記が参考になりました
iOS 11 WKWebView 3大新機能 (WWDC 2017) - Qiita

iOS11未満に関しては以前こちらにまとめました
WKWebViewでのSessionの共有 - Qiita

3. alert & confirm

JavaScripのalertやconfirmはデフォルトでは無視されてしまうので、
以下のようにデリゲートメソッドを実装し、アクションをハンドリングする必要があります

  // window.alert()
  func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
  }

  // window.confirm()
  func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {

  }

4. target="_blank"

WKWebViewでは target="_blank" 時に無反応になってしまうので、
以下のようにデリゲートメソッドを実装し、アクションをハンドリングする必要があります

  func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    guard let url = navigationAction.request.url else {
      return nil
    }
    guard let targetFrame = navigationAction.targetFrame, targetFrame.isMainFrame else {
      webView.load(URLRequest(url: url))
      return nil
    }
    return nil
  }

5. カメラロール&フォトライブラリへのアクセスを許可

カメラロールやフォトライブラリにアクセスする必要がある場合は Info.plist
NSCameraUsageDescriptionNSPhotoLibraryUsageDescription を記述し、
アクセス時にダイアログに表示される説明文を設定する必要があります

  <key>NSCameraUsageDescription</key>
  <string>カメラロールにアクセスする説明文</string>
  <key>NSPhotoLibraryUsageDescription</key>
  <string>フォトライブラリにアクセスする説明文</string>

6. 位置情報へのアクセスを許可

位置情報にアクセスする際は、 CoreLocation のデリゲートメソッドを実装し、
またこの時、位置情報を利用する説明文を Info.plist に記述する必要があります
設定された説明文は、位置情報へのアクセスが求められたときに表示されるダイアログに表示されます

import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
  lazy var locationManager: CLLocationManager = { [unowned self] in
        var manager = CLLocationManager()
        manager.delegate = self
        return manager
  }()

  func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
      locationManager.requestWhenInUseAuthorization()
    case .restricted, .denied:
      break
    case .authorizedAlways, .authorizedWhenInUse:
      break
    }
  }
}

起動中のみ位置情報を取得する場合

  <key>NSLocationWhenInUseUsageDescription</key>
  <string>起動中に位置情報を利用する説明文</string>

常に位置情報を取得する場合

  <key>NSLocationAlwaysUsageDescription</key>
  <string>常にに位置情報を利用する説明文</string>

7. URLスキーマ経由で別アプリを起動

電話番号やメールアドレスなど、URLスキーマ経由でアプリを開くためには
リンクの形式を判別してから処理を実行する必要があります

extension ViewController: WKNavigationDelegate {
  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
      isAllow = false
      return
    }
    if (/* 外部アプリを開く場合 */) {
      UIApplication.shared.open(url, options: [:], completionHandler: nil)
      decisionHandler(.cancel)
    }
    decisionHandler(.allow)
  }
}

8. ビデオのインライン再生

WKWebView の初期設定では動画のインライン再生に対応していないので、
WKWebViewConfigurationallowsInlineMediaPlaybacktrue に設定する必要があります

※ 参考
iOS10のWKWebViewではビデオのインライン再生はできない→できます! - swift life

9. 初回のloadが失敗した場合にreloadが実行されない対策

ネットワークが接続されていなかったときなど初回のloadが失敗した場合、WKWebView のurlプロパティの値がnilに設定されていまい、
reload時の処理が無視されてしまいます
WKWebView のurlプロパティの値がnilかどうかを検証し、nilの場合は新規にloadすることで意図した挙動が実現できます

  func reload() {
    if let url = webview.url {
        webview.reload()
    } else {
        webview.load(URLRequest(url: originalURL))
    }
  }

※ 参考
ios - WKWebView reload() can't refresh current page - Stack Overflow