2

Currently, if I want to create a directory hierarchy in Document directory, I would perform the following


Using NSSearchPathForDirectoriesInDomains (Works fine)

let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let documentUrl1 = URL(string: documentsDirectory)!
//
// /Users/yccheok/Library/Developer/...
//
print("documentUrl1 -> \(documentUrl1)")

let dataPath = documentUrl1.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
    do {
        try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    } catch {
        print(error.localizedDescription)
    }
}

But, if I use the following

Using FileManager.default.urls (Not working)

let documentUrl0 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
//
// file:///Users/yccheok/Library/Developer/...
//
print("documentUrl0 -> \(documentUrl0)")

let dataPath = documentUrl0.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
    do {
        try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    } catch {
        print(error.localizedDescription)
    }
}

The following error will be printed

You can’t save the file “SubFolder2” because the volume is read only.


I was wondering, under what use case, that FileManager.default.urls will be useful? Thanks.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

1 Answers1

5

URL(string and absoluteString are the wrong APIs to work with file system paths.

Your code accidentally works because URL(string applied to a path creates a path rather than a valid URL, and absoluteString applied to a path returns the path because there is no scheme (file://).

However you are strongly discouraged from doing that. A file system URL must be created with URL(fileURLWithPath and you can get the path with the path property.

You are encouraged to use the FileManager API because it provides a better error handling and it provides also the URL related API which is preferred over the string path API.

This is the correct FileManager way to create the directory if it doesn't exist

let fm = FileManager.default
do {
    let documentUrl = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    //
    // file:///Users/yccheok/Library/Developer/...
    //
    print("documentUrl -> \(documentUrl)")
    
    let dataURL = documentUrl.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
    print("dataPath.path -> \(dataURL.path)")
    if !fm.fileExists(atPath: dataURL.path) {
        try fm.createDirectory(at: dataURL, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    }
} catch { print(error) }

In iOS 16+, macOS 13+ you can replace

let documentUrl = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
...
let dataURL = documentUrl.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")

with

let documentUrl = URL.documentsDirectory
...
let dataURL = documentUrl.appending(components: "SubFolder1", "SubFolder2")
vadian
  • 274,689
  • 30
  • 353
  • 361
  • I have delelete the created folders, and re-test with my first example code, and your proposed code. Both works fine for me. It seems my `documentUrl1.appendingPathComponent(...).absoluteString` are having same value as your `dataURL.path`. – Cheok Yan Cheng Mar 18 '21 at 05:17
  • 1
    Nevertheless `absoluteString` is wrong. Don't use it with file system URLs. Your code accidentally works because `URL(string` applied to a path creates a path, not an URL. So the `absoluteString` API doesn't cause harm. But once again, it's wrong. – vadian Mar 18 '21 at 05:19