How to find NSDocumentDirectory in Swift?

137,880

Solution 1

Apparently, the compiler thinks NSSearchPathDirectory:0 is an array, and of course it expects the type NSSearchPathDirectory instead. Certainly not a helpful error message.

But as to the reasons:

First, you are confusing the argument names and types. Take a look at the function definition:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directory and domainMask are the names, you are using the types, but you should leave them out for functions anyway. They are used primarily in methods.
  • Also, Swift is strongly typed, so you shouldn't just use 0. Use the enum's value instead.
  • And finally, it returns an array, not just a single path.

So that leaves us with (updated for Swift 2.0):

let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]

and for Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]

Solution 2

Swift 3.0 and 4.0

Directly getting first element from an array will potentially cause exception if the path is not found. So calling first and then unwrap is the better solution

if let documentsPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
    //This gives you the string formed path
}

if let documentsPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    //This gives you the URL of the path
}

Solution 3

The modern recommendation is to use NSURLs for files and directories instead of NSString based paths:

enter image description here

So to get the Document directory for the app as an NSURL:

func databaseURL() -> NSURL? {

    let fileManager = NSFileManager.defaultManager()

    let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

    if let documentDirectory: NSURL = urls.first as? NSURL {
        // This is where the database should be in the documents directory
        let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("items.db")

        if finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) {
            // The file already exists, so just return the URL
            return finalDatabaseURL
        } else {
            // Copy the initial file from the application bundle to the documents directory
            if let bundleURL = NSBundle.mainBundle().URLForResource("items", withExtension: "db") {
                let success = fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL, error: nil)
                if success {
                    return finalDatabaseURL
                } else {
                    println("Couldn't copy file to final location!")
                }
            } else {
                println("Couldn't find initial database in the bundle!")
            }
        }
    } else {
        println("Couldn't get documents directory!")
    }

    return nil
}

This has rudimentary error handling, as that sort of depends on what your application will do in such cases. But this uses file URLs and a more modern api to return the database URL, copying the initial version out of the bundle if it does not already exist, or a nil in case of error.

Solution 4

Xcode 8.2.1 • Swift 3.0.2

let documentDirectoryURL =  try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

Xcode 7.1.1 • Swift 2.1

let documentDirectoryURL =  try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)

Solution 5

Usually I prefer to use this extension:

Swift 3.x and Swift 4.0:

extension FileManager {
    class func documentsDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as [String]
        return paths[0]
    }
    
    class func cachesDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) as [String]
        return paths[0]
    }
}

Swift 2.x:

extension NSFileManager {
    class func documentsDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as [String]
        return paths[0]
    }
    
    class func cachesDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true) as [String]
        return paths[0]
    }
}
Share:
137,880

Related videos on Youtube

Ivan R
Author by

Ivan R

Updated on August 05, 2021

Comments

  • Ivan R
    Ivan R almost 3 years

    I'm trying to get path to Documents folder with code:

    var documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory:0,NSSearchPathDomainMask:0,true)
    

    but Xcode gives error: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

    I'm trying to understand what is wrong in the code.

    • Eric Aya
      Eric Aya over 7 years
      There were several edits to this question which were adding possible solutions. The whole thing was a mess. I've rolled back to the first version for clarity. Answers don't belong in a question and should be posted as answers. I'm available to discuss if someone thinks my rollback is too radical. Thanks.
  • Daniel T.
    Daniel T. over 9 years
    This answer failed in Xcode 6.0. The cast must be to NSString rather than String.
  • nschum
    nschum over 9 years
    @DanielT. Just tried again and String works in Xcode 6.0 and 6.1. Generally, String and NSString are bridged automatically in Swift. Maybe you had to cast to NSString for another reason.
  • Daniel T.
    Daniel T. over 9 years
    Did you try it in a playground or in an actual app? The results are different. The code casts to a String in a playground, but not in an app. Check out question (stackoverflow.com/questions/26450003/…)
  • nschum
    nschum over 9 years
    Both, actually. Could be a subtle bug in Swift, I suppose ... I'll just edit the answer to be safe. :) Thanks
  • János
    János about 9 years
    running again the app generates a different path, why?: (1) /var/mobile/Containers/Data/Application/9E18A573-6429-434D-9‌​B42-08642B643970/Doc‌​uments (2) /var/mobile/Containers/Data/Application/77C8EA95-B77A-474D-8‌​522-1F24F854A291/Doc‌​uments
  • paneer_tikka
    paneer_tikka almost 9 years
    For Swift NuB's (like me), the first two arguments of the NSSearchPathForDirectoriesInDomains function are of type enum NSSearchPathDirectory and struct NSSearchPathDomainMask respectively. Hence Swift allows you to drop the enum/struct name before the dot. That is why NSSearchPathDomainMask.UserDomainMask is equivalent to .UserDomainMask within the context of this function call.
  • Duncan Groenewald
    Duncan Groenewald over 7 years
    @János did you every find out why ?? I have just started seeing this same problem, every time I run the app in the debugger it changes the path name !? Weird - but I guess just never save the full path, always recreate the path to the file each time the app starts up !?
  • Iulian Onofrei
    Iulian Onofrei over 7 years
    Or even FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
  • Andrey Gordeev
    Andrey Gordeev over 7 years
    I don't think it's necessary to instantiate a new file manager every time we want to get an URL for Documents directory.
  • Iulian Onofrei
    Iulian Onofrei over 7 years
    I thought it's the same thing. Thanks!
  • Leo Dabus
    Leo Dabus almost 7 years
    absoluteString is wrong. to get the file path fro a fileURL you need to get its .path property
  • Bogdan
    Bogdan over 6 years
    This is not an issue, this is security reasons. iOS changes App container each launch.
  • B.Saravana Kumar
    B.Saravana Kumar about 6 years
    where to call this?
  • Alessandro Ornano
    Alessandro Ornano about 6 years
    To every part of your code you need: let path = FileManager.documentsDir()+("/"+"\(fileName)") , you can call it without differences between thread (main or background).