Search code examples
iosamazon-s3swiftuipdf-viewer

Downloading public PDF file from S3 Bucket SwiftUI


I am simply trying to store a static PDF in an S3 bucket which I can grab and present using Apple's built-in PDF Viewer. I am a having problems and a bit confused on a way to store the PDF locally in the proper form. I apologize if this is repeated or too simple, I have searched for hours on a proper solution but have not found anything that works. Thank you. I tried using the URL directly but that also threw an error.

import Amplify
import SwiftUI
import WebKit



struct ContentView: View {

    
    var body: some View {
        containedView()
        }
    
    func grabPDF(){
        Amplify.Storage.downloadData(
            key: "TermsOfUse.pdf"
        ){ result in
            switch result{
            case .success(let key):
                
                print("File with key: \(key)")
            case .failure(let storageError):
                print("Failed: ", storageError)
            }
        }
    }
    
    
    func containedView() -> WebView{
        grabPDF()
        return WebView(request: openPDF())
    }
    
    func openPDF() -> URLRequest{
        let path = Bundle.main.path(forResource: "TermsOfUse", ofType: "pdf")
        let url = URL(fileURLWithPath: path!)
        print(url)
        return URLRequest(url: url)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct WebView: UIViewRepresentable{
    let request: URLRequest
    
    func makeUIView(context: Context) -> WKWebView{
        return WKWebView()
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context){
        uiView.load(request)
    }
}

Solution

    1. Amplify.Storage.downloadData returns a Data object -- it doesn't actually download the file. But, there's another method downloadFile that will work for this.

    2. Your applications Bundle has a static set of files. Once you save a file, you won't be looking in the Bundle, but rather in the app's documents or temp directory.

    3. Rather than making the Amplify calls in the view body, probably better to assign it to a separate object (here I'm using an ObservableObject called DataLoader) to do the work at then set a flag (downloaded) when it's done.

    class DataLoader : ObservableObject {
        @Published var downloaded : Bool = false
        
        func makeRequest() {
            let downloadToFileName = getTermsOfUseURL()
    
            Amplify.Storage.downloadFile(
                key: "TermsOfUse.pdf",
                local: downloadToFileName,
                progressListener: { progress in
                    print("Progress: \(progress)")
                }, resultListener: { event in
                    switch event {
                    case .success:
                        print("Completed")
                        DispatchQueue.main.async { self.downloaded = true }
                    case .failure(let storageError):
                        print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)")
                    }
                })
        }
        
        func getTermsOfUseURL() -> URL {
            let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            return paths[0].appendingPathComponent("TermsOfUse.pdf")
        }
    }
    
    struct ContentView: View {
        @ObservedObject var loader = DataLoader()
        
        var body: some View {
            VStack {
                if loader.downloaded {
                    WebView(request: URLRequest(url: loader.getTermsOfUseURL()))
                }
            }.onAppear {
                loader.makeRequest()
            }
        }
    }
    

    Note: I have not tested the Amplify code; it is assumed to work as it is taken from https://docs.amplify.aws/lib/storage/download/q/platform/ios