적당한 고통은 희열이다

- 댄 브라운 '다빈치 코드' 중에서

초보 iOS 개발자의 일상/개발 업무

[swift iOS] WKWebView file download 웹뷰 파일 다운로드 (feat. FileManager)

hongssup_ 2021. 11. 3. 17:02
반응형

웹뷰에서 파일 다운로드가 되지 않는 문제가 발생했다. 

웹뷰에서의 파일 다운로드 버튼을 클릭 시 다운로드가 아니라 해당 파일 url 링크로 이동해버리는? 것이었다. 

안드로이드는 웹뷰에 파일 다운로드 리스너를 설정할 수 있도록 되어있다고 하는데, iOS는... 

여기저기 찾아보아도 엄청 깔끔하게 아 이거다! 하는 방법을 찾지 못했다.

여러 방법을 짜집기 하여

webview의 decidePolicyFor navigationAction에서 받아오는 url이 .pdf 등의 파일 형식으로 끝나면 해당 url로 이동을 하는 것이 아니라 파일 앱으로 다운로드 되도록 다음과 같이 구현하였다. 

 

1. webView의 decidePolicyFor navigationAction에서 파일 형식 감지하기 

이게 최선인지는 모르겠지만 나는 다음과 같이 구현해주었다. 

받아온 url의 맨 뒤에 다음과 같은 파일 형식 확장자가 있으면 decisionHandler(.cancel) 하여 페이지로 로딩 되는 것을 막아준다. 

public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        let extension4 = "\(url)".suffix(4)
        let extension5 = "\(url)".suffix(5)        
        
        if extension4 == ".pdf" || extension4 == ".csv" || extension5 == ".pptx" || extension5 == ".xlsx"{
            print("fileDownload: redirect to download events.")
            //파일 다운로드
            decisionHandler(.cancel)
            return
        }
    }
    decisionHandler(.allow)
}

참고 : Download embedded PDF loaded in WKWebView

 

2. FileManager 이용하여 앱 내 파일 저장 경로 받아오기

아이폰 내장 Documents 디렉토리 안에 파일을 저장하기 위해 Document Directory 주소를 받아온다. 

let fileManager = FileManager.default
let ducumentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]

참고 : ZeddiOS - FileManager 이용해 파일 만드는 법

 

3. 파일 이름 정하여 파일 경로 생성하기 

1) url에서 파일 이름 받아오기

보통의 파일 url에서는 "https://hongssup.tistory.com/download/파일이름.pdf" 와 같이 url의 맨 뒤에 파일명과 확장자가 포함되어 있는 경우가 많다. 이럴 경우, 다음과 같이 url.lastPathComponent 를 사용하여 파일 이름을 가지고 올 수 있다. 

그런다음 앞서 찾아준 파일 앱의 경로에 파일 이름을 붙여 파일 주소를 만들어준다. 

let fileName = url.lastPathComponent
let fileURL = documentsURL.appendingPathComponent(fileName)

2) query에서 파일 이름 받아오기

하지만 나의 경우, url에 파일명이 포함되어있지 않고, 쿼리에 파일명이 저장되어 있어 다음과 같이 URL에 extension으로 쿼리 파라미터를 받아오는 함수를 추가해주어 파일 이름과 확장자를 받아오도록 했다. 

파일 명이 한글로 되어있을 경우 이름이 깨지는 현상이 발생하여 파일명.removingPercentEncoding 으로 인코딩을 해주었다. 

let fileName = url.queryValue("filename")
let fileURL = documentsURL.appendingPathComponent("\(fileName!.removingPercentEncoding!)")
extension URL {
    func queryValue(_ queryParam:String) -> String? {
        guard let url = URLComponents(string: self.absoluteString) else { return nil }
        return url.queryItems?.first(where: { $0.name == queryParam })?.value
    }
}

* 만들어진 파일 주소 예시 : file:///var/mobile/Containers/Data/Application/Documents/파일이름.pdf

참고 : StackOverflow - Get value of URL parameters

 

4. 파일 경로 지정 및 다운로드 옵션 설정

우선 import Alamofire를 해준 후 다음과 같이
파일 다운로드 경로를 지정해주고, 이전 파일 삭제하고 디렉토리를 생성하는 옵션을 설정해준다. 

let destination: DownloadRequest.Destination = { _, _ in
    return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

 

5. 다운로드

URLSession 대신에 Alamofire를 이용하여 파일을 다운로드 해주었다.  

AF.download(url, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination).downloadProgress { (progress) in
    print("progress: \(Int(progress.fractionCompleted * 100))")
}.response { response in
    if response.error != nil {
        print("파일다운로드 실패")
    } else {
        print("파일다운로드 완료")
    }
}

참고 : GonsLab - Swift Alamofire 사용하기

 


여기까지 하면 파일 앱에 다운로드는 잘 된다. 

그런데!! 웹뷰 화면에서 아무 일도 일어나지 않는다. 

다운로드 완료 후 파일 앱이든 프리뷰든 띄워줘야할 것 같은데..

우선 다음과 같이 Info.plist에서 Application supports iTunes file sharing / Supports opening documents in place 권한을 다 YES로 바꿔주면 된다고 한다. 

이렇게 권한만 설정해주면 자동으로 파일 앱이 뜨는 줄 알았는데..

응 안돼~ 

몰라 설정을 더 해줘야하는건지 파일 다 다운받아도 아무 변화가 없다. 

open file in document  file app 검색해도 딱히 나오지 않더라. 

내가 원하는 건 파일 앱 내의 특정 파일을 여는건데 filePicker만 뜨고.. 

그래서 우선 임시방편?으로 파일 앱에 다운로드는 받되, 앱에서는 파일 앱을 여는 대신 그냥 프리뷰 화면을 보여주기로 했다. 

documentInteractionController를 이용하여 자동으로 프리뷰를 띄우는 방법도 있다고 하는데, 나는 개인적으로 모달 방식을 좋아하기에 새로 FIleViewController를 만들어 거기서 webview.load(request)로 파일을 .pagesheet 모달 형식으로 띄워주었다. 

 

전체코드 

public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        
        let extension4 = "\(url)".suffix(4)
        let extension5 = "\(url)".suffix(5)        
        
        if extension4 == ".pdf" || extension4 == ".csv" || extension5 == ".pptx" || extension5 == ".xlsx"{
            print("fileDownload: redirect to download events.")
            let fileManager = FileManager.default
            let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileName = url.queryValue("filename")
            let fileURL = documentsURL.appendingPathComponent("\(fileName!.removingPercentEncoding!)")
            
            let destination: DownloadRequest.Destination = { _, _ in
                return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
            }
            
            AF.download(url, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination).downloadProgress { (progress) in
                print("progress: \(Int(progress.fractionCompleted * 100))")
            }.response { response in 
                if response.error != nil {
                    print("파일다운로드 실패")
                } else {
                    print("파일다운로드 완료")
                }
            }
            decisionHandler(.cancel)
            return
        }
    }
    decisionHandler(.allow)
}

extension URL {
    func queryValue(_ queryParam:String) -> String? {
        guard let url = URLComponents(string: self.absoluteString) else { return nil }
        return url.queryItems?.first(where: { $0.name == queryParam })?.value
    }
}

참고 : Mr.후 - iOS WKWebView PDF Download Viewer

 

혹시나 제 글이 도움이 되셨다면 하트 한번 눌러주시면 감사하겠습니다 🥰

iOS 개발자분들 모두 화이팅입니다👍🏻

728x90
반응형