WebViewで画像のダウンロードボタンを押された時にアルバムに保存する

はじめに

WebView,SafariViewどちらもデフォルトの状態で画像を長押しすると保存することができるので、画像の長押しを禁止していない限りデフォルトの保存方法で良いと思います。

画像の長押しが禁止されていてかつSafariViewではやりたくなりという時にボタンタップ時に画像のみのページに遷移せずに直でダウンロードを進めたい時の解決策です。

コード

import UIKit
import WebKit

class ImageDownloadWebViewController: UIViewController {

    var request: URLRequest

    // MARK: - Initializer

    init(url: URL) {
        request = URLRequest(url: url)
        request.httpMethod = "GET"

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - LifeCycle

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //WebViewの中で長押しメニューを出さないようにする
        let disableCalloutScriptString = "document.documentElement.style.webkitTouchCallout='none';"
        let disableCalloutScript = WKUserScript(source: disableCalloutScriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

        //ユーザーコンテントのインスタンスを作成し入れる
        let userContentController = WKUserContentController()
        userContentController.addUserScript(disableCalloutScript)

        let webConfig = WKWebViewConfiguration()
        webConfig.userContentController = userContentController

        let webView = WKWebView(frame: .zero, configuration: webConfig)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view.addFull(view: webView)

        webView.load(request)
    }
}

// MARK: - WKUIDelegate, WKNavigationDelegate

extension ImageDownloadWebViewController: WKUIDelegate, WKNavigationDelegate {

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        let urlString: String = String(describing: navigationAction.request.url)
        // 対象の画像かを判定
        if urlString.contains(".jpg") {
            do {
                let data = try Data(contentsOf: navigationAction.request.url!)
                guard let image = UIImage(data: data) else {
                    return
                }
                
                showDialog(image: image)
                
            } catch let err {
                print("Error : \(err.localizedDescription)")
            }
            decisionHandler(.cancel)
            return
        }

        decisionHandler(.allow)
    }

    private func showDialog(image: UIImage) {
        let alert: UIAlertController = UIAlertController(title: "画像を保存しますか?", message: "", preferredStyle: UIAlertController.Style.alert)
        let confirmAction: UIAlertAction = UIAlertAction(title: "確定", style: UIAlertAction.Style.default, handler: { _ in
            UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.showResultOfSaveImage(_:didFinishSavingWithError:contextInfo:)), nil)
        })
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel, handler: { _ in
            print("キャンセル")
        })
        alert.addAction(confirmAction)
        alert.addAction(cancelAction)
        
        present(alert, animated: true, completion: nil)
    }

    @objc func showResultOfSaveImage(_ image: UIImage, didFinishSavingWithError error: NSError!, contextInfo: UnsafeMutableRawPointer) {

        if error != nil {
            return
        }
        
        let alert = UIAlertController(title: "アルバムに保存しました", message: "", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        
        present(alert, animated: true, completion: nil)

    }
    
}

extension UIView {
    func addFull(view: WKWebView) {
        view.translatesAutoresizingMaskIntoConstraints = false
        addSubview(view)
        view.topAnchor.constraint(equalTo: topAnchor, constant: 0.0).isActive = true
        view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0).isActive = true
        view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0).isActive = true
        view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0).isActive = true
    }
}

Permission

Privacy - Photo Library Additions Usage Description

解説

検証にはアラド戦記の壁紙ダウンロードページを使用させていただきました。

やっていることはシンプルで以下のメソッドでaタグで画像リンクが設定されているボタンを踏んだ際、urlの中身を判定して”.jpg”という文字列が含まれている場合遷移を行わずURLをImageに変換して保存処理をかけています。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)