Almost every application needs to persist data in the application's sandbox. One option is to store data in the Documents directory of the application container. The question is How do you get the location of the Documents directory in Swift? This is easier than you might think.
Because applications are sandboxed on Apple's platforms, you need to be careful where you store data. To the get location of the application's sandbox on the user's device, you invoke the NSHomeDirectory()
function, defined in the Foundation framework. The location this function returns differs from platform to platform. Let's take a look at the location of the application's sandbox on iOS and tvOS.
I have created a blank Xcode project using the Single View App template. We invoke the NSHomeDirectory()
function in the application(_:didFinishLaunchingWithOptions:)
method of the AppDelegate
class and print the result to Xcode's console.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print(NSHomeDirectory())
return true
}
The output looks something like this.
/Users/Bart/Library/Developer/CoreSimulator/Devices/853BED98-B81B-48CB-9158-6E4BACE17E7C/data/Containers/Data/Application/3EF124CF-6B25-4093-A49C-BC5EF91A9D8C
We don't want to store data at the root of the application container, though. The Documents directory is a better place to store data. We could append Documents to the path returned by the NSHomeDirectory()
function, but it's a better idea to ask the FileManager
class for the location of the Documents directory. This is slightly more verbose.
We ask the default file manager instance for the location of the Documents directory by invoking the urls(for:in:)
method. This method accepts two arguments, the directory we are searching for and a domain mask. This may sound complicated, but it's straightforward. We pass documentDirectory
as the first parameter because we want to obtain the location of the Documents directory. We pass userDomainMask
as the second parameter, which means we search for the Documents directory in the user's home directory.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
The file manager returns an array of URL
objects. We know that there is only one Documents directory in the home directory of the application container. With that in mind, we can ask the array of URL
objects for the item at index 0
.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = urls[0]
Let's print the value to Xcode's console and run the application in the simulator. The output confirms that we successfully obtained the URL pointing to the Documents directory. The examples are written in Swift 5, but they also apply to earlier Swift versions.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print(NSHomeDirectory())
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = urls[0]
print(documentsDirectory)
return true
}
Adding a Bit of Convenience
If you need to access the Documents directory in several locations of your project, then it makes sense to create a convenience method for easier access. It's up to you what that convenience method looks like. In the following example, I define a static method on the URL
struct using an extension. The static method with name documents
provides easy access to the Documents directory in Swift.
extension URL {
static var documents: URL {
return FileManager
.default
.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
}
Storing an Image in the Documents Directory
This makes it trivial to access the Documents directory and write, for example, the data of an image to disk. We create an image by loading an asset from the asset catalog. We then create the URL
object, using the static method we defined in the extension for the URL
struct, appending the file name we choose for the image. We convert the UIImage
instance to a Data
object by invoking the pngData()
method on the UIImage
instance and write the image data to the Documents directory.
// Create Image
let image = UIImage(named: "landscape")
// Create URL
let url = URL.documents.appendingPathComponent("image.png")
// Convert to Data
if let data = image?.pngData() {
do {
try data.write(to: url)
} catch {
print("Unable to Write Image Data to Disk")
}
}
The write(to:)
method is a throwing method, which means we need to wrap it in a do-catch
statement. Add this snippet to the application(_:didFinishLaunchingWithOptions:)
method of the AppDelegate
class and run the application in the simulator. I use SimPholders to easily access the sandbox of the application. This is what that looks like in Finder.