iOS 14.5のWKWebViewでBlobオブジェクトをダウンロードする
Webブラウザアプリ開発者待望の新機能
iOS 14.5未満のWKWebViewはblob:
ではじまるURLのダウンロード、つまりJavaScriptで生成されたBlobオブジェクトのダウンロードにネイティブ対応していませんでした。BlobオブジェクトはWKWebView上のメモリに存在するため、単純にURLを指定するだけではダウンロードできず、以下のようにWKWebViewと連携するJavaScriptを実行してData URLに変換するなどの工夫が必要でした。
2021年4月27日にリリースされたiOS 14.5から、WKWebViewや関連クラスにダウンロード機能を実装するためのAPIがいくつか追加されました。それらを使うことで、Blobオブジェクトのダウンロードもネイティブコードのみで実装可能になりました。
Blobオブジェクトをダウンロードするための手順
以下がXcode 12.5とSwift 5.4で実装する手順です。わかりやすいようにSwiftUIではなくUIKitベースで実装しています。また、各デリゲートメソッドの実装はWKWebViewをサブビューに持つViewControllerクラスで行っています。
なお、ここではBlobオブジェクトのダウンロード元サイトとしてScratch 3.0のエディター画面を使っています。ファイルメニューから「コンピューターに保存する」を選択すると、プロジェクトファイルをsb3
の拡張子を持つBlobオブジェクトとしてダウンロードできます。
エディター画面: https://scratch.mit.edu/projects/editor/
手順1. WKNavigationDelegateの実装
WKNavigationDelegateプロトコルのwebView(_:decidePolicyFor:preferences:decisionHandler:)
を以下のように実装します。blob:
ではじまるURLがリクエストされたときに、decisionHandler()
の引数としてWKNavigationActionPolicyに新しく追加された.download
を返すと、ダウンロードの準備がはじまります。
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.request.url?.scheme == "blob" {
decisionHandler(.download)
} else {
decisionHandler(.allow)
}
}
...
そうすると、新しいデリゲートメソッドであるwebView(_:navigationAction:didBecome:)
が呼ばれるようになります。このメソッドにWKDownload
というWKWebView上でのダウンロードを担当するクラスのインスタンスが渡されるので、そのdelegate
プロパティを指定してあげます。これにより、ダウンロード先の指定などができるようになります。
...
func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
download.delegate = self
}
}
手順2. WKDownloadDelegateの実装
WKDownload用に新規追加されたWKDownloadDelegateプロトコルのdownload(_:decideDestinationUsing:suggestedFilename:completionHandler:)
を以下のように実装します。suggestedFilename
によって提示されたファイル名からダウンロード先のURLを決定し、completionHandler()
に渡してあげます。ここではtmp
ディレクトリ直下にそのまま保存するようにしてみました。
extension ViewController: WKDownloadDelegate {
func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
let url = FileManager.default.temporaryDirectory.appendingPathComponent(suggestedFilename)
completionHandler(url)
}
}
手順3. WKWebViewでダウンロード元のURLを開く
あとはこれまでのWKWebViewの使い方と同じです。delegate
プロパティを指定し、ダウンロード元のURLをリクエストします。その後、Blobオブジェクトのダウンロードがはじまる操作(今回は「コンピューターに保存する」)をすると、ファイルのダウンロードがはじまり、指定したURLにファイルが保存されます。
import UIKit
import WebKit
class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
let url = URL(string: "https://scratch.mit.edu/projects/editor/")!
webView.load(URLRequest(url: url))
}
}
おまけ1: Blobオブジェクト以外もダウンロードできるようにする
WKNavigationActionに新しくshouldPerformDownload
というプロパティが追加されており、それを使うとダウンロード対象のリクエストかどうかを判断することができます。blob:
のときにはこれがtrue
になるので、webView(_:decidePolicyFor:preferences:decisionHandler:)
を以下のように実装することもできます。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.shouldPerformDownload {
decisionHandler(.download)
} else {
decisionHandler(.allow)
}
}
おまけ2: 任意のURLをダウンロードする
WKWebViewに追加されたstartDownload(using:completionHandler:)
を使って、任意のURLをダウンロードすることもできます。ダウンロードの準備ができるとcompletionHandler
に指定したクロージャーが呼ばれるので、そこに渡されるWKDownloadのインスタンスにdelegate
を指定してあげれば、WKDownloadDelegateプロトコルの各デリゲートメソッドが呼ばれるようになります。
webView.startDownload(using: URLRequest(url: URL(string: "https://scratch.mit.edu/")!)) { download in
download.delegate = self
}
Discussion