【swift4】WKWebViewリファレンス日本語訳

公式ドキュメントをすごく軽く訳したものを少し加筆しつつ、書いていきます。
公式ドキュメントは以下から。
> https://developer.apple.com/documentation/webkit/wkwebview

# WKWebView
> WKWebViewオブジェクトは、アプリ内ブラウザなどのインタラクティブなWebコンテンツを表示することができます。 WKWebViewクラスを使用して、Webコンテンツをアプリケーションに埋め込むことができます。これを行うには、WKWebViewオブジェクトをコードから作成し、それをビューとして設定し、Webコンテンツのロードを行います。

つまり、アプリ内でWebページを扱う場合、とりあえず、これを使えって感じですね。現在WKWebViewはストーリーボードで生成できないので、コードから生成してViewを適用する形になります。

# 概要

> **Important**
> iOS8.0およびOS X 10.10以降、WKWebViewを使用してください。 UIWebViewまたはWebViewは使用しないでください。

UIWebViewなどは非推奨なようです。

> [init(frame:configuration:)](https://developer.apple.com/documentation/webkit/wkwebview/1414998-init) メソッドで、新しいWKWebViewオブジェクトを作成した後、Webコンテンツを読み込むことができます。
[loadHTMLString（_：baseURL :)](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring) メソッドを使用してローカルのHTMLファイルの読み込みを開始するか、または[load(_:)](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load)メソッドを呼び出してWebコンテンツの読み込みを開始します。
読み込みを停止するには[stopLoading（）](https://developer.apple.com/documentation/webkit/wkwebview/1414981-stoploading)メソッドを使用し、ロード中は[isLoading](https://developer.apple.com/documentation/webkit/wkwebview/1414964-isloading)プロパティを使用してWebビューが読み込み中かどうかを調べルことができます。
デリゲートプロパティを[WKUIDelegate](https://developer.apple.com/documentation/webkit/wkuidelegate)プロトコルに準拠するオブジェクトに設定して、Webコンテンツの読み込みを追跡します。プログラムでWKWebViewを作成する例は、[リスト1](https://developer.apple.com/documentation/webkit/wkwebview#2560973)を参照してください。

web上のコンテンツだけでなく、ローカルのHTMLも読み込むことができるんですね。
また、読み込みの監視や、読み込みの停止も行えるようです。
リスト1というのは、以下のサンプルコードになります。

```swift:Listing1 
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string: "https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }}
```
実行してみました。動作は以下のようです。
![wktest.gif](https://qiita-image-store.s3.amazonaws.com/0/180836/e84be2b3-b39a-685a-eba4-9be184f7fbe6.gif)


> ユーザがWebページの履歴の「戻る」「進む」を行うには、[goBack()](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback)と[goForward()](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward)をボタンのアクション内で使ってください。
また、「戻る」「進む」ができない場合(最初のページや最新のページ)では該当のボタンを非表示にするなどしたい時、それぞれ[canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback)と[canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward)で監視することができます。

> デフォルトでは、WebビューはWebコンテンツに表示される電話番号を電話リンクに自動的に変換します。電話リンクがタップされると、電話アプリが起動してその番号にダイヤルします。この設定をオフにするには、[dataDetectorTypes](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/1641937-datadetectortypes)プロパティを[WKDataDetectorTypes](https://developer.apple.com/documentation/webkit/wkdatadetectortypes)で[phoneNumber](https://developer.apple.com/documentation/webkit/wkdatadetectortypes/1641917-phonenumber)フラグを含まないように設定してください。

> また、Webビューに最初に表示されたときにWebコンテンツの倍率を[setMagnification(_:centeredAt:)](https://developer.apple.com/documentation/webkit/wkwebview/1414996-setmagnification)で設定することができます。その後、ユーザはジェスチャを用いて倍率を変更することができます。

デフォルトの設定を色々いじれるみたいですね。

# WKWebViewのメソッド、プロパティ

## WebKitがコンテンツをロードできるかどうかの判定
### class func handlesURLScheme(_ urlScheme: String) -> Bool
WebKitがネイティブで特定のURLスキームでリソースを読み込むことをサポートしているかどうかを返します。

## WebViewの初期化
### var configuration: WKWebViewConfiguration
Webビューを初期化するための設定のコピー。

### init(frame: CGRect, configuration: WKWebViewConfiguration)
指定されたframeと設定で初期化されたWebビューを返します。

### init?(coder: NSCoder)
概要なし

生成の流れとしては、以下のようになるかと思います。

```swift
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
```


## WKWebViewのプロパティ

### var scrollView: UIScrollView
Webビューに関連したスクロールビューです。
ここにデリゲートして、下にスクロールしたか、上にスクロールしたか、などを検知することができます。  
TwitterのWebViewとかは、これを検知して、下にスクロールした場合は、ヘッダーとフッターを収納、上にスクロールした場合は展開しています。つまり、閲覧時の表示領域を大きくとることでユーザエクスペリエンスの向上を狙っているんですね。

### var title: String?
ページタイトル

### var url: URL?
アクティブなURL

### var customUserAgent: String?
ユーザーエージェントの名前。SafariとかiPhone7とか。

### var serverTrust: SecTrust?
現在コミットされているナビゲーションのSecTrustRefオブジェクト。

### ~~var certificateChain: [Any]~~
Deprecated。
現在コミットされているナビゲーションの証明書チェーンを構成するオブジェクトの配列。

## Delegateの設定
### var navigationDelegate: WKNavigationDelegate?
webビューのナビゲーションのデリゲート。

- 遷移開始時
- ページの読み込み開始時
- ページの読み込み完了時
- 読み込みエラー発生時
- リダイレクト時

などのイベントを受け取りたい場合はこのDelegateを継承します。
以下、実装例。

```swift
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate ,WKNavigationDelegate{

    var webView: WKWebView!

    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view = webView
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        let myURL = URL(string: "https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }}

    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        print("遷移開始")
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("読み込み完了")
        print(webView.title as Any)
    }
}
```


### var uiDelegate: WKUIDelegate?
webビューのユーザインターフェースのデリゲート。
これはとりあえず継承しておくやつです。注意としては、

```swift
webView.delegate = self
```

ではなく、

```swift
webView.uiDelegate = self
```

とすること。stackoverflowで「あれもこれもやったのに、全然表示されない！」と悩んでるコードが上記のミスを犯していて、真顔で指摘されていました。



## コンテンツの読み込み

### var estimatedProgress: Double
現在のナビゲーションで、どれだけ読み込まれたかの推定値。0.0-1.0の間をとります。

### var hasOnlySecureContent: Bool
ページ上のすべてのリソースが安全に暗号化された接続を介してロードされているかどうかを示すブール値。

### func loadHTMLString(String, baseURL: URL?)
WebページのコンテンツとベースURLを設定します。

### var isLoading: Bool
ビューが現在コンテンツをロードしているかどうかを示すブール値。

### func reload()
現在のページを再読み込みします。

### func reloadFromOrigin()
> 可能であれば、キャッシュの有効性の問い合わせ(cache-validating conditionals)を行い。エンドツーエンドの再検証(end-to-end revalidation)を行い、現在のページをリロードします。

よくわかりません。

### func stopLoading()
現在のページの全てのリソースの読み込みを停止します。

### func load(Data, mimeType: String, characterEncodingName: String, baseURL: URL)
WebページのコンテンツとベースURLを設定します。

### func loadFileURL(URL, allowingReadAccessTo: URL)
ファイルシステム上の要求されたファイルのURLに移動します。
例えば、ローカルファイルを見やすい形で閲覧したい時に使えます。
Xlsxファイルとかいけます。

```swift
# 例えば、xcode上で追加したファイルを開いてみる。
let path: String = Bundle.main.path(forResource: "hogehoge", ofType: "xlsx")!
let url = URL(fileURLWithPath: path)
webView.loadFileURL(url, allowingReadAccessTo: url)
```


## コンテンツの拡大縮小

### var allowsMagnification: Bool
拡大ジェスチャがWebビューの倍率を変更するかどうかを示すブール値。
おそらく、最大倍率や最小倍率でさらに拡大や縮小しようとする時falseになるんじゃないでしょうか。

### var magnification: CGFloat
現在のページの倍率。


### func setMagnification(CGFloat, centeredAt: CGPoint)
指定された倍率でページを拡大縮小する。その際、centeredAtで指定された座標を中央に配置する。

## Webページの遷移
### var allowsBackForwardNavigationGestures: Bool
水平方向のスワイプジェスチャーがback-forwardのナビゲーションをトリガーするかどうかのブール値。

### var backForwardList: WKBackForwardList
Webビューの back-forward リスト。進むや戻るでの遷移先の候補がリストで入っているのでしょう。
履歴という感覚で大丈夫だと思います。

### var canGoBack: Bool
back-forward リストにナビゲートできるバックアイテムがあるかどうかを示すブール値。
つまり、戻れるかどうかを示します。

### var canGoForward: Bool
back-forward リストにナビゲートできるフォワードアイテムがあるかどうかを示すブール値。
つまり、進めるかどうかを示します。


### var allowsLinkPreview: Bool
3D Touchでリンクを押すと、リンク先のプレビューが表示されるかどうかを決定するブール値。
最後まで強く押すとWKWebViewではなくSafariで立ち上がるようです。

### func goBack()
ページを戻る。back-forward リストから選ぶ。

**ここで注意なのが、このメソッドを呼び出し、遷移が完了した時、必ずしもdidFinishLoadのイベントメソッドが呼び出されるわけではないということ。** 
> [webViewDidFinishLoad is not fired when I use goBack](https://stackoverflow.com/questions/15584093/webviewdidfinishload-is-not-fired-when-i-use-goback)

WebViewは何ページ分かのページ情報のキャッシュを持っており、読み込みを行うかどうかはキャッシュ次第になってしまいます。なので、goBackなどを行う時は、必ずしもdidFinishメソッドが呼び出される訳ではないことに注意してコーディングする必要があります。


### func goForward()
ページを進む。back-forward リストから選ぶ。

### func go(to: WKBackForwardListItem)
back-forward リストから項目に移動し、現在の項目として設定します。
履歴内の特定のページに遷移できるようですね。

### func load(URLRequest)
指定のURLから遷移する。引数がURLRequestなので、以下のようにURLRequestを生成する必要があるでしょう。

```swift
let myURL = URL(string: "https://www.apple.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
```


## JavaScriptの実行
### func evaluateJavaScript(String, completionHandler: ((Any?, Error?) -> Void)? = nil)
JavaScriptをStringで与えれば、実行してくれるようです。変態的なことをしようと思ったら、ここを弄る必要があるでしょう。

例として、userAgentを取得してみます。

```swift
webView.evaluateJavaScript("navigator.userAgent", completionHandler: {(res,err) in
            let res = res as! String
            print(res)
        })

# 実行結果
# Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79
```

## スナップショットを撮る
### func takeSnapshot(with: WKSnapshotConfiguration?, completionHandler: (UIImage?, Error?) -> Void)
スクリーンショットが撮れるみたいですね。contextでレンダリングする必要がなくなりました。
(2018/04/21 追記)
**スナップショット試しました。**Webページの読み込みが終わった段階で表示させたかったので、WKNavigationDelegateの`webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)`で呼び出します。
画像は以下のように取得できます。

```swift
webView.takeSnapshot(with: nil) { (image, error) in
                self.imageView.image = image
            }
```
ただ、ここで取得したimageが**真っ白になる**問題が発生。
どうやら、didFinishメソッドは読み込み終了時に呼び出されてはいるんですが、その読み込んだコンテンツが描画されるまでには、もう少し時間がかかるみたいです。これは実験的に得た知見なので根拠はありません。

なので、didFinishで呼び出す場合には、遅延実行をしてあげましょう。僕はこれでうまく行きました。

```swift
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0){
    webView.takeSnapshot(with: nil) { (image, error) in
        self.imageView.image = image
    }
}
```


# 余談系

## userAgentの取得、変更

取得は以下のよう。

```swift
webView.evaluateJavaScript("navigator.userAgent", completionHandler: {(res,err) in
            let res = res as! String
            print(res)
        })

# 実行結果
# Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79
```

変更は結構難しいみたいで、以下のようです。 [参考](https://qiita.com/furu8ma/items/addfe075d5286ccaa364)


```swift:AppDelegate.swift
# didFinishLaunchingWithOptions内に記述
NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent" : "Custom Agent"])
```