Nawaf
Nawaf

Reputation: 159

Swift IOS - How can I obtain permission to upload a document when testing on a device?

I am integrating a documentPicker into my IOS app. Selected files will be uploaded using Firebase Storage. I am getting this error when attempting to upload the file while testing the app on my iPhone:

Error uploading file: The file “file.pdf” couldn’t be opened because you don’t have permission to view it.

On the other hand, am not getting this or any other error when testing using the simulator, and the error occurs whether I select a file from iCloud or on local storage.

Here is the code for picker:

struct DocumentPicker: UIViewControllerRepresentable {

@Binding var filePath: URL?

func makeCoordinator() -> DocumentPicker.Coordinator {
    return DocumentPicker.Coordinator(parent1: self)
}

func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
    let picker = UIDocumentPickerViewController(documentTypes: ["public.item"], in: .open)
    picker.allowsMultipleSelection = false
    picker.delegate = context.coordinator
    return picker
}

func updateUIViewController(_ uiViewController: DocumentPicker.UIViewControllerType, context: UIViewControllerRepresentableContext<DocumentPicker>) {
}

class Coordinator: NSObject, UIDocumentPickerDelegate {
    
    var parent: DocumentPicker
    
    init(parent1: DocumentPicker){
        parent = parent1
    }
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        // Here is where I get the path for the file to be uploaded
        parent.filePath = urls[0]
        print(urls[0].absoluteString)
    }
}

}

Here is the upload function, and where the error is caught:

do {
            let fileName = (PickedDocument?.lastPathComponent)!
            let fileData = try Data(contentsOf: PickedDocument!)
            let StorageRef = Storage.storage().reference().child(uid + "/" +  doc.documentID + "/" + fileName)
            StorageRef.putData(fileData, metadata: nil) { (metadata, error) in
                guard let metadata = metadata else {
                    return
                }
                StorageRef.downloadURL { (url, error) in
                    guard let urlStr = url else{
                        completion(nil)
                        return
                    }
                    let urlFinal = (urlStr.absoluteString)
                    ShortenUrl(from: urlFinal) { result in
                        if (result != "") {
                            print("Short URL:")
                            print(result)
                            completion(result)
                        }
                        else {
                            completion(nil)
                            return
                        }
                    }
                }
            }
        }
        catch {
            print("Error uploading file: " + error.localizedDescription)
            self.IsLoading = false
            return
        }
    }

Obviously there is some sort of permission that I need to request in order to be able to access and upload files on a physical iPhone, but am not sure how to get that? I have tried adding the following in info.plist but it still didn't work.

<key>NSDocumentsFolderUsageDescription</key>
<string>To access device files for upload upon user request</string>

Upvotes: 3

Views: 4067

Answers (1)

Nawaf
Nawaf

Reputation: 159

I figured it out based on an answer I found here.

What I was doing is: I was storing a reference to the local file, which is a security scoped resource. Hence the permission error was thrown.

What I did to get around this: At the moment when the user picks the file, I will use url.startAccessingSecurityScopedResource() to start accessing its content and make a copy of the file instead of holding its reference.

Here is the updated code for the picker didPickDocumentsAt:

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard controller.documentPickerMode == .open, let url = urls.first, url.startAccessingSecurityScopedResource() else { return }
        defer {
            DispatchQueue.main.async {
                url.stopAccessingSecurityScopedResource()
            }
        }
        
        do {
            let document = try Data(contentsOf: url.absoluteURL)
            parent.file = document
            parent.fileName = url.lastPathComponent
            print("File Selected: " + url.path)
        }
        catch {
            print("Error selecting file: " + error.localizedDescription)
        }
        
    }

And then my Storage upload function is:

func UploadFile(doc: DocumentReference, completion:@escaping((Bool?, String?) -> () )) {
    do {
        let StorageReference = Storage.storage().reference().child(self.User + "/" +  doc.documentID + "/" + fileName!)
        StorageReference.putData(file!, metadata: nil) { (metadata, error) in
            if let error = error {
                self.alertMessage = error.localizedDescription
                self.showingAlert = true
                completion(false, nil)
            }
            else {
                StorageReference.downloadURL { (url, error) in
                    if let error = error {
                        self.alertMessage = error.localizedDescription
                        self.showingAlert = true
                        completion(false, nil)
                    }
                    else {
                        ShortenUrl(from: url!.absoluteString) { result in
                            if (result != "") {
                                completion(true, result)
                            }
                            else {
                                completion(false, nil)
                            }
                        }
                    }
                }
            }
        }
    }
    catch {
        self.alertMessage = "Error uploading file: " + error.localizedDescription
        self.showingAlert = true
        completion(false, nil)
    }
}

Upvotes: 3

Related Questions