How to find NSDocumentDirectory in Swift?
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
anddomainMask
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:
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]
}
}
Related videos on Youtube
Ivan R
Updated on August 05, 2021Comments
-
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 over 7 yearsThere 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. over 9 yearsThis answer failed in Xcode 6.0. The cast must be to
NSString
rather thanString
. -
nschum over 9 years@DanielT. Just tried again and
String
works in Xcode 6.0 and 6.1. Generally,String
andNSString
are bridged automatically in Swift. Maybe you had to cast toNSString
for another reason. -
Daniel T. over 9 yearsDid 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 over 9 yearsBoth, actually. Could be a subtle bug in Swift, I suppose ... I'll just edit the answer to be safe. :) Thanks
-
János about 9 yearsrunning 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
-
paneer_tikka almost 9 yearsFor Swift NuB's (like me), the first two arguments of the NSSearchPathForDirectoriesInDomains function are of type
enum NSSearchPathDirectory
andstruct NSSearchPathDomainMask
respectively. Hence Swift allows you to drop the enum/struct name before the dot. That is whyNSSearchPathDomainMask.UserDomainMask
is equivalent to.UserDomainMask
within the context of this function call. -
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 over 7 yearsOr even
FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
-
Andrey Gordeev over 7 yearsI 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 over 7 yearsI thought it's the same thing. Thanks!
-
Leo Dabus almost 7 years
absoluteString
is wrong. to get the file path fro a fileURL you need to get its.path
property -
Bogdan over 6 yearsThis is not an issue, this is security reasons. iOS changes App container each launch.
-
B.Saravana Kumar about 6 yearswhere to call this?
-
Alessandro Ornano about 6 yearsTo every part of your code you need:
let path = FileManager.documentsDir()+("/"+"\(fileName)")
, you can call it without differences between thread (main or background).