166

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.

Shruti Thombre
  • 989
  • 4
  • 11
  • 27
Ivan R
  • 1,923
  • 5
  • 18
  • 15
  • 1
    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. – Eric Aya Nov 23 '16 at 10:49

10 Answers10

264

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]
nschum
  • 15,322
  • 5
  • 58
  • 56
  • This answer failed in Xcode 6.0. The cast must be to `NSString` rather than `String`. – Daniel T. Oct 19 '14 at 16:15
  • 1
    @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. – nschum Oct 19 '14 at 19:10
  • 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 (http://stackoverflow.com/questions/26450003/swift-get-documents-directory-returns-empty-string/26452472#26452472) – Daniel T. Oct 19 '14 at 19:21
  • Both, actually. Could be a subtle bug in Swift, I suppose ... I'll just edit the answer to be safe. :) Thanks – nschum Oct 19 '14 at 19:33
  • 3
    running again the app generates a different path, why?: (1) `/var/mobile/Containers/Data/Application/9E18A573-6429-434D-9B42-08642B643970/Documents` (2) `/var/mobile/Containers/Data/Application/77C8EA95-B77A-474D-8522-1F24F854A291/Documents` – János Apr 28 '15 at 16:54
  • 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. – paneer_tikka Jun 10 '15 at 11:55
  • @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 !? – Duncan Groenewald Nov 04 '16 at 22:51
  • This is not an issue, this is security reasons. iOS changes App container each launch. – Bogdan Oct 31 '17 at 07:34
48

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
}
Fangming
  • 24,551
  • 6
  • 100
  • 90
33

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.

Abizern
  • 146,289
  • 39
  • 203
  • 257
24

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)
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
23

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]
    }
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
14

For everyone who looks example that works with Swift 2.2, Abizern code with modern do try catch handle of error

func databaseURL() -> NSURL? {

    let fileManager = NSFileManager.defaultManager()

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

    if let documentDirectory:NSURL = urls.first { // No use of as? NSURL because let urls returns array of NSURL
        // This is where the database should be in the documents directory
        let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("OurFile.plist")

        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("OurFile", withExtension: "plist") {

                do {
                    try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
                } catch let error as NSError  {// Handle the error
                    print("Couldn't copy file to final location! Error:\(error.localisedDescription)")
                }

            } else {
                print("Couldn't find initial database in the bundle!")
            }
        }
    } else {
        print("Couldn't get documents directory!")
    }

    return nil
}

Update I've missed that new swift 2.0 have guard(Ruby unless analog), so with guard it is much shorter and more readable

func databaseURL() -> NSURL? {

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

// If array of path is empty the document folder not found
guard urls.count != 0 else {
    return nil
}

let finalDatabaseURL = urls.first!.URLByAppendingPathComponent("OurFile.plist")
// Check if file reachable, and if reacheble just return path
guard finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) else {
    // Check if file is exists in bundle folder
    if let bundleURL = NSBundle.mainBundle().URLForResource("OurFile", withExtension: "plist") {
        // if exist we will copy it
        do {
            try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
        } catch let error as NSError { // Handle the error
            print("File copy failed! Error:\(error.localizedDescription)")
        }
    } else {
        print("Our file not exist in bundle folder")
        return nil
    }
    return finalDatabaseURL
}
return finalDatabaseURL 
}
mm24
  • 9,280
  • 12
  • 75
  • 170
Roman Safin
  • 704
  • 7
  • 16
14

More convenient Swift 3 method:

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
5

Xcode 8b4 Swift 3.0

let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
Siavash Alp
  • 1,412
  • 15
  • 14
4

Usually i prefer like below in swift 3, because i can add file name and create a file easily

let fileManager = FileManager.default
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
    let databasePath = documentsURL.appendingPathComponent("db.sqlite3").path
    print("directory path:", documentsURL.path)
    print("database path:", databasePath)
    if !fileManager.fileExists(atPath: databasePath) {
        fileManager.createFile(atPath: databasePath, contents: nil, attributes: nil)
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Hamid Zandi
  • 2,714
  • 24
  • 32
0

Copy and paste this line in App delegate like this and it will print path like this

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String)
        return true
    }

Copy the path and paste it in go To Folder in finder by right clicking on it then enter enter image description here

Open the file in Xcode

enter image description here

Quick learner
  • 10,632
  • 4
  • 45
  • 55