I'm developing an iOS app (latest Swift, iOS, Xcode versions atm of writing) which needs to rename audio files (and later modify its metadata) located anywhere (local storage, external storage, cloud, etc). I'm bringing my own question in StackOverflow but I'd like to know better from the source so I came here.
The trouble comes when testing on a physical device while trying to rename a file in some folder On My iPhone, the app does as intended when testing on the Simulator. On my physical iPhone I get an error logged saying that I don't have the permissions to do that.
“filename.mp3” couldn’t be moved because you don’t have permission to access “randomFolder".
I did my googling and asked GPTs about it, learned about FileCoordinator, UIDocumentPickerViewController, startAccessingSecurityScopedResource. I also saw a couple of videos that I cannot reference in this forum.
Some code I have in place:
This is the document picker which I then call from a .sheet on another View.
struct DocumentPicker: UIViewControllerRepresentable {
@Binding var newName: String
@EnvironmentObject private var bookmarkController: BookmarkController
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = context.coordinator
return documentPicker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {
print("updateUIViewController documentPicker")
}
func makeCoordinator() -> Coordinator {
Coordinator(self, newName)
}
class Coordinator: NSObject, UIDocumentPickerDelegate {
var parent: DocumentPicker
var newName: String
init(_ parent: DocumentPicker, _ newName: String = "") {
self.parent = parent
self.newName = newName
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
// save bookmark
print("documentPicker \(urls[0])")
parent.bookmarkController.addBookmark(for: urls[0])
// Rename the file
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: urls[0], options: [], error: &error) { coordinatedURL in
do {
// let data = try Data(contentsOf: newURL)
print("urls[0]: \(urls[0])")
print("coordinatedURL: \(coordinatedURL)")
print("renamedURL: \(newName)")
try renameFile(at: coordinatedURL, to: newName)
} catch {
print("Error: \(error.localizedDescription)")
}
}
}
}
}
Renaming function:
/// Rename selected file from browser
func renameFile(at fileURL: URL, to newName: String) throws {
let fileExtension = fileURL.pathExtension
let directory = fileURL.deletingLastPathComponent()
// Create a new URL with the updated name and the original extension
let renamedURL = directory.appendingPathComponent(newName).appendingPathExtension(fileExtension)
try FileManager.default.moveItem(at: fileURL, to: renamedURL)
}
I have a BookmarkController in place so that my URLs are bookmarked for later use. Here it is:
import SwiftUI
import MobileCoreServices
class BookmarkController: ObservableObject {
@Published var urls: [URL] = []
init() {
loadAllBookmarks()
}
func addBookmark(for url: URL) {
print("adding bookmark for \(url)")
do {
guard url.startAccessingSecurityScopedResource() else {
print("Failed to obtain access to the security-scoped resource.")
return
}
defer { url.stopAccessingSecurityScopedResource() }
let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil)
let uuid = UUID().uuidString
try bookmarkData.write(to: getAppSandboxDir().appendingPathComponent(uuid))
urls.append(url)
} catch {
print("Error Adding Bookmark: \(error.localizedDescription)")
}
}
func loadAllBookmarks() {
// Get all the bookmark files
let files = try? FileManager.default.contentsOfDirectory(at: getAppSandboxDir(), includingPropertiesForKeys: nil)
// Map over the bookmark files
self.urls = files?.compactMap { file in
do {
let bookmarkData = try Data(contentsOf: file)
var isStale = false
// Get the URL from each bookmark
let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale)
guard !isStale else {
// Handle stale bookmarks
return nil
}
print("loaded bookmark: \(url)")
// Return URL
return url
} catch {
print("Error Loading Bookmark: \(error.localizedDescription)")
return nil
}
} ?? []
}
private func getAppSandboxDir() -> URL {
// TODO ver 0 index
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
}
When running on my device, I see url.startAccessingSecurityScopedResource throwing:
Failed to obtain access to the security-scoped resource.
I have also tried getting access to the parent directory like this:
let parentURL = url.deletingLastPathComponent()
and then using parentURL instead, but it also fails.
Something I noticed is that my app is not shown in the Settings -> Privacy & Security -> Files and Folders list. My app didn't trigger any dialog to be added here given user consent.
Fiddling with entitlements in Info.plist and Capabilities did not help either, but I'm not so sure about which ones should I add.
So:
Could it be that the problem I'm seeing about permissions and my app not appearing at the Files and Folders privacy settings on my iPhone are because my app is signed with a developer account which is not in the apple developer program? this is my last thought since I'm not understanding what else should I try.
Any pointers and help will be much appreciated.
Thanks!