iosswifturlnsfilemanager

URLResourceValues not working for renaming a file


I have to rename some files in a directory in the Documents directory. I'm trying with URLResourceValues:

let fileURLs = try! FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
fileURLS.forEach { fileURL in
    // calculate newFileName
    var resourceValues = URLResourceValues()
    resourceValues.name = newFilename
    var mutableFileURL = fileURL
    try! mutableFileURL.setResourceValues(resourceValues)
}

After setting resourceValues.name I see this in the console...

key NSMutableString "NSURLNameKey"  0x00000001e7a8a368
value   NSString    "newName.jpg"   0x0000000283f61fe0

So that part is working. The try! completes without crashing so there were no errors thrown. But mutableFileURL is unchanged. It's got the old name, not the new name.

I see in the docs that setting read-only properties or setting properties not supported will be ignored and will not throw an error. But in my research I see this approach used commonly for renaming files. And I don't think it's a write access thing because if I use the old way it works fine:

try! FileManager.default.moveItem(at: fileURL, to: newFileURL)

What could I be missing here?

P.S. app targets iOS 14, running on a real device running iOS 16.1


Solution

  • You have not shown how you know things didn't work. But you say:

    But mutableFileURL is unchanged

    Your code isn't supposed to change mutableFileURL. If you're looking at mutableFileURL to see what happened, that's the problem.

    The URL here is just a pointer to the file on disk. That's the whole point of the setResourceValues approach. Your code changes the name of the file on disk (and mutableFileURL, the pointer, is now invalid and should just be thrown away).

    If you re-fetch the contents of the documents directory, you'll see that your code worked just fine.

    Complete example, showing both what I think you are doing and how to do this correctly:

    // --- create the file ---
    let fm = FileManager.default
    let docsurl = try fm.url(
        for: .documentDirectory, in: .userDomainMask,
        appropriateFor: nil, create: false
    )
    
    let newurl = docsurl.appending(path: "howdy.txt")
    try "howdy".write(to: newurl, atomically: true, encoding: .utf8)
    
    let arr = try fm.contentsOfDirectory(
        at: docsurl, includingPropertiesForKeys: nil
    )
    arr.forEach { print($0.lastPathComponent) }
    // howdy.txt
    
    // --- rename the file ---
    var values = URLResourceValues()
    values.name = "hello.txt"
    var mutableurl = newurl
    try mutableurl.setResourceValues(values)
    
    // --- how did we do? this is _not_ the way:
    print(mutableurl)
    // file:///.../Documents/howdy.txt
    
    // --- how did we do? this _is_ the way
    let arr2 = try fm.contentsOfDirectory(
        at: docsurl, includingPropertiesForKeys: nil
    )
    arr2.forEach { print($0.lastPathComponent) }
    // hello.txt