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

## 概要

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

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

## 1. HTTP通信

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

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

※ 参考
[必須化まで約2ヵ月半！App Transport Securityについて _ セキュリティ対策のラック](https://www.lac.co.jp/lacwatch/people/20161017_001057.html)


## 2. Cookie管理

iOS11以降は下記が参考になりました
[iOS 11 WKWebView 3大新機能 (WWDC 2017) - Qiita](https://qiita.com/ShingoFukuyama/items/c2d33ff3fc36dbdb69a4)

iOS11未満に関しては以前こちらにまとめました
[WKWebViewでのSessionの共有 - Qiita](https://qiita.com/nikadon/items/485b02d412d9d21adf6c)

## 3. alert & confirm

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

```swift
  // 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"` 時に無反応になってしまうので、
以下のようにデリゲートメソッドを実装し、アクションをハンドリングする必要があります

```swift    
  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` に
`NSCameraUsageDescription` と `NSPhotoLibraryUsageDescription` を記述し、
アクセス時にダイアログに表示される説明文を設定する必要があります

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

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

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

```swift
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
    }
  }
}
```

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

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

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

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

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

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

```swift
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` の初期設定では動画のインライン再生に対応していないので、 
`WKWebViewConfiguration` の `allowsInlineMediaPlayback` を `true` に設定する必要があります

※ 参考
[iOS10のWKWebViewではビデオのインライン再生はできない→できます！ - swift life](http://swiftlife.hatenablog.jp/entry/2016/10/23/234318)

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

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

```swift
  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](https://stackoverflow.com/questions/45706866/wkwebview-reload-cant-refresh-current-page/45708014)