본문 바로가기

iOS

[iOS,Swift] WebView 파일 다운로드 구현하기

 

iOS 에서 파일다운로드를 해서 다른 앱이나 Airdrop 등으로 공유하는 방향으로 구현해 보려고 한다

 

WebKit 의 WKNavigationDelegate를 상속받아 navigationResponse 에서 mimeType이 있을때 

파일 다운로드를 구현하는 방식으로 하면 되겠다 

 

우선 mimeType 구조체를 만들고 다운로드로 인식할 값을 init 해준다 

 

struct MimeType {
    var type:String
    var fileExtension:String
}

...
var mimeTypes:[MimeType] = []

...
mimeTypes = [MimeType(type: "ms-excel", fileExtension: "xls")
	,MimeType(type: "pdf", fileExtension: "pdf")
    ,MimeType(type: "PDF", fileExtension: "PDF")
    ,MimeType(type: "application/octet-stream", fileExtension: "PDF")
    ,MimeType(type: "application/x-msdownload", fileExtension: "")]

MimeType에서 type은 웹에서 뿌려지는 mimeType을 넣어주고 fileExtenstion은 해당 타입의 확장자명을 추후에 만들어줄때 사용한다.

 

 

WKNavigationDelegate 를 상속받아 WebView의 Delegate 연결

webView.navigationDelegate = self

오버라이드된

webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void)  구현 하자

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let mimeType = navigationResponse.response.mimeType {
        if isMimeTypeConfigured(mimeType) {
            if let url = navigationResponse.response.url {
                var fileName = getDefaultFileName(forMimeType: mimeType)
                if let name = getFileNameFromResponse(navigationResponse.response) {
                    let nName = name as NSString
                    fileName = nName.removingPercentEncoding ?? name
                    fileName = fileName.replacingOccurrences(of: "\";", with: "")
                    fileName = fileName.replacingOccurrences(of: "\"", with: "")
                }
                downloadData(fromURL: url, fileName: fileName) { success, destinationURL in
                    if success, let destinationURL = destinationURL {
                        self.fileDownloadedAtURL(url: destinationURL)
                    }
                }
                decisionHandler(.cancel)
                return
            }
        }
    }
    decisionHandler(.allow)
}

private func isMimeTypeConfigured(_ mimeType:String) -> Bool {
    for record in self.mimeTypes {
        if mimeType.contains(record.type) {
            return true
        }
    }
    return false
}

private func getDefaultFileName(forMimeType mimeType:String) -> String {
    for record in self.mimeTypes {
        if mimeType.contains(record.type) {
            return "default." + record.fileExtension
        }
    }
    return "default"
}

private func getFileNameFromResponse(_ response:URLResponse) -> String? {
    if let httpResponse = response as? HTTPURLResponse {
        let headers = httpResponse.allHeaderFields
        if let disposition = headers["Content-Disposition"] as? String {
            let components = disposition.components(separatedBy: "filename=")
            if components.count > 1 {
                return components[1]
            }
        }
    }
    return nil
}

private func downloadData(fromURL url:URL,
                              fileName:String,
                              completion:@escaping (Bool, URL?) -> Void) {
    webView.configuration.websiteDataStore.httpCookieStore.getAllCookies() { cookies in
        let session = URLSession.shared
        session.configuration.httpCookieStorage?.setCookies(cookies, for: url, mainDocumentURL: nil)
        let task = session.downloadTask(with: url) { localURL, urlResponse, error in
            if let localURL = localURL {
                let destinationURL = self.moveDownloadedFile(url: localURL, fileName: fileName)
                completion(true, destinationURL)
            }
            else {
                completion(false, nil)
            }
        }

        task.resume()
    }
}

private func moveDownloadedFile(url:URL, fileName:String) -> URL {
    let tempDir = NSTemporaryDirectory()
    let destinationPath = tempDir + fileName
    let destinationURL = URL(fileURLWithPath: destinationPath)
    try? FileManager.default.removeItem(at: destinationURL)
    try? FileManager.default.moveItem(at: url, to: destinationURL)
    return destinationURL
}


func fileDownloadedAtURL(url: URL) {
    DispatchQueue.main.async {
        let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
        activityVC.popoverPresentationController?.sourceView = self.view
        activityVC.popoverPresentationController?.sourceRect = self.view.frame
        activityVC.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
        self.present(activityVC, animated: true, completion: nil)
    }
}

 

navigationResponse.response.mimeType  에서 처음 설정한 값이 있으면 파일 이름을 추출하고 

url을 통해서 파일을 다운로드 받는 구조이다

 

downloadData 함수를 통해서 세션과 쿠키를 연동시키고 downloadTask를 실행하고 

moveDownloadFile을 통해서 파일을 다운로드하게 된다 

다운로드가 된 파일을 filedownloadedAtURL을 통해서 공유를 하게 되는 그러한 방식이다