I am trying to download a file using AlamoFire and save it to a downloads directory of the user's choice (like safari). However, whenever I set the download directory to a folder outside of my app's documents, I get the following error (on a real iOS device):
downloadedFileMoveFailed(error: Error Domain=NSCocoaErrorDomain Code=513 "“CFNetworkDownload_dlIcno.tmp” couldn’t be moved because you don’t have permission to access “Downloads”." UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Containers/Data/Application/A24D885A-1306-4CE4-9B15-952AF92B7E6C/tmp/CFNetworkDownload_dlIcno.tmp, NSUserStringVariant=(Move), NSDestinationFilePath=/private/var/mobile/Containers/Shared/AppGroup/E6303CBC-62A3-4206-9C84-E37041894DEC/File Provider Storage/Downloads/100MB.bin, NSFilePath=/private/var/mobile/Containers/Data/Application/A24D885A-1306-4CE4-9B15-952AF92B7E6C/tmp/CFNetworkDownload_dlIcno.tmp, NSUnderlyingError=0x281d045d0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}, source: file:///private/var/mobile/Containers/Data/Application/A24D885A-1306-4CE4-9B15-952AF92B7E6C/tmp/CFNetworkDownload_dlIcno.tmp, destination: file:///private/var/mobile/Containers/Shared/AppGroup/E6303CBC-62A3-4206-9C84-E37041894DEC/File%20Provider%20Storage/Downloads/100MB.bin)
The summary of that error is that I don't have permission to access the folder I just granted access to.
Here's my attached code:
import SwiftUI
import UniformTypeIdentifiers
import Alamofire
struct ContentView: View {
@AppStorage("downloadsDirectory") var downloadsDirectory = ""
@State private var showFileImporter = false
var body: some View {
VStack {
Button("Set downloads directory") {
showFileImporter.toggle()
}
Button("Save to downloads directory") {
Task {
do {
let destination: DownloadRequest.Destination = { _, response in
let documentsURL = URL(string: downloadsDirectory)!
let suggestedName = response.suggestedFilename ?? "unknown"
let fileURL = documentsURL.appendingPathComponent(suggestedName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
let _ = try await AF.download(URL(string: "https://i.imgur.com/zaVQDFJ.png")!, to: destination).serializingDownloadedFileURL().value
} catch {
print("Downloading error!: \(error)")
}
}
}
}
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [UTType.folder]) { result in
switch result {
case .success(let url):
downloadsDirectory = url.absoluteString
case .failure(let error):
print("Download picker error: \(error)")
}
}
}
}
To reproduce (Run on a REAL iOS device!):
- Click the
Set downloads directory
button toOn my iPhone
- Click the
Save to downloads directory
button - Error occurs
Upon further investigation, I found that safari uses the Files and Folders
privacy permission (Located in Settings > Privacy > Files and folders
on an iPhone) to access folders outside the app sandbox (This link for the image of what I'm talking about). I scoured the web as much as I can and I couldn't find any documentation for this exact permission.
I have seen non-apple apps (such as VLC) use this permission, but I cannot figure out how it's granted.
I tried enabling the following plist properties, but none of them work (because I later realized these are for macOS only)
<key>NSDocumentsFolderUsageDescription</key>
<string>App wants to access your documents folder</string>
<key>NSDownloadsFolderUsageDescription</key>
<string>App wants to access your downloads folder</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
Can someone please help me figure out how to grant the files and folder permission and explain what it does? I would really appreciate the help.
LSSupportsOpeningDocumentsInPlace
andUIFileSharingEnabled
and it seems you have done this so it should be working. I would say delete your app do a clean build and run it again and check if a folder with your app name and logo appears on the files app. – ScheeliteFiles and Folders
permission in settings. I'm just not sure how to grant it. Thank you nevertheless. – Bogeyman