Photo by Fredy Jacob on Unsplash

Reading, Writing, and Deleting Files in Swift

Corey Davis
6 min readMar 8, 2019

Most of today’s average apps communicate via HTTP. While we can read and write data this way, it is still very common to need to save data to files locally on the device. Let’s talk about how that is done and then address an easier way.

TL;DR

If you are not interested in learning how to do it yourself then check out my File project on GitHub (but you really should know how it is done, so keep reading!).

FileManager and URLs

When first starting out with file handling in iOS it can feel daunting. You have to learn the FileManager which doesn’t seem so bad until you find out that you need to pass it URLs. You then stop and think “wait, what do URLs have to do with saving files? This is confusing.”

Apple chose to use URLs as the universal language for communicating the location of files in the file system. Once you get your head wrapped around the concept, it really is quite ingenious. Working with file locations via strings can be quite messy (and I have many dozens of lines of old server code for traversing directories and files to prove it), but using URLs wraps it all in a much needed structure that is relatively easy to understand and familiar if you have previously used URLs for networking. And even if you haven’t used URLs before, what we need to get the basics going is really quite easy. In fact, it can all be boiled down to a pattern containing three simple steps:

1. Create the destination directory URL
2. Use the directory URL to create the file URL
3. Save the data to the file URL

Let’s take a look at how this is done.

Note: If you have never used FileManager before, just know that it uses the Singleton pattern and that you do not instantiate FileManager directly, but instead you use FileManager.default to call FileManager’s properties and functions. If you are unfamiliar with the Singleton pattern that’s okay, it is just a way to ensure that only one instance of a class is ever instantiated in the application’s lifecycle.

Directory URL

To start with we need a URL for the documents directory. You don’t have to save everything to the documents directory, but it is the most logical place to start and once you know how to save there it is fairly straightforward to save to other locations. We’ll use the following code to get the URL to the documents directory:

let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

What we are doing here is asking FileManager for a list of URL’s for the documents directory in the user’s home directory (or, in the case of iOS, the app’s home directory). This returns an array of which the first entry will contain the documents directory which is why we specify the first array index of [0] at the end of the statement. This will return a URL object for the documents directory. It’s really that simple.

File URL

Now that we have the URL to the directory were the file will be saved, let’s create a URL to the file itself. To accomplish this we use URL(fileURLWithPath:relativeTo:). The path referred to in the fileURLWithPath parameter is the file’s name excluding any file extension. For instance, if we want a file named “myFile.txt” the fileURLWithPath would be “myFile” (I’ll address the “.txt” extension in just a moment). Next, the relativeTo parameter is the directory to which the file will be saved. This parameter takes a URL and, you guessed it, we will simply pass in the URL that we got above:

let fileURL = URL(fileURLWithPath: “myFile”, relativeTo: directoryURL)

Okay, so what about that file extension? To add that simply add appendingPathExtension(:) to the end of the call like this:

let fileURL = URL(fileURLWithPath: “myFile”, relativeTo: directoryURL).appendingPathExtension(“txt”)

That will give us a URL to myFile.txt in the app’s documents directory folder and do so in just two lines of code. That wasn’t too bad, right?

Saving Data

Now, let’s actually save some data:

// Create data to be saved
let myString = “Saving data with FileManager is easy!”
guard let data = myString.data(using: .utf8) else {
print(“Unable to convert string to data”)
return
}
// Save the data
do {
try data.write(to: fileURL)
print(“File saved: \(fileURL.absoluteURL)”)
} catch {
// Catch any errors
print(error.localizedDescription)
}

First, we create a data object containing the data that we want to save. In this case a simple string. We then convert the string into data. Next, we use a do/catch block to wrap the writing of the data just in case there is an error. As you can see, the actual writing of the data is quite simple and done by passing the file URL as a parameter to Data’s write(:) function. Finally, we’ll print the error if one occurs.

And that’s it. Assuming you already have your data created, this can all be done in just a few lines of code.

Reading Data

Now that we have saved the data, at some point we will want to read it back. It turns out that this is just as easy as saving it, especially now that we have the file URL:

do {
// Get the saved data
let savedData = try Data(contentsOf: fileURL)
// Convert the data back into a string
if let savedString = String(data: savedData, encoding: .utf8) {
print(savedString)
}
} catch {
// Catch any errors
print(“Unable to read the file”)
}

Deleting Data

Now that we have saved a file and read its contents, let’s look at how we delete that file. Once again, it is really quite simple.

try FileManager.default.removeItem(at: fileURL)

We simply pass the fileURL object that we created earlier into FileManager’s removeItem(at:) method. This method does not return any values, but does throw an error if it is unable to delete the file so I suggest that you wrap it in a do/catch block.

How Can This Get Easier?

As promised, saving, reading, and deleting data isn’t that difficult. But I did say I would show you an even easier way, so what is it?

On my GitHub I have a open-source project called File which is essentially a wrapper around FileManager that can be used to save, read, or delete a file in just two lines of code:

let fileURLComponents = FileURLComponents(
fileName: “mySecondFile”,
fileExtension: “txt”,
directoryName: nil,
directoryPath: .documentDirectory)
try? File.write(data, to: fileURLComponents)

So, what is happening here? First, we are creating a FileURLComponents object which is a construct that is part of File. It essentially allows you to input all of the parameters that you would normally use to build the FileManager URL object and from these File will build the URL internally. The fileName parameter is the name of the file, the fileExtension parameter is the file extension, the directoryName is the name of the sub-directory that the file should be saved to relative to the directoryPath. In this case we passed nil to directoryName so the file will be stored directly in the documents directory just as before. Finally, you send the data and the URL components to the File.write function which handles creating the URL and saving the data. In a real app you will want to wrap File.write in a do statement with proper error handling.

Reading the data from the saved file is just as easy. Assuming you have the FileURLComponents already, you simply use the File.read function:

let savedData = try? File.read(from: fileURLComponents)

Finally, deleting files are probably exactly what you would expect it to be at this point:

try File.delete(fileURLComponents)

And that is pretty much all there is to it.

Where to Go From Here

If you are feeling adventurous, continue reading more about FileManager and URLs. Learning the Apple provided APIs is one of the best things you can do to make yourself a better iOS developer. Even if you use a wrapper like File, I highly encourage you to learn FileManager because there is much more that can be done with FileManager such as directory traversal and other operations which I plan to cover in future articles.

For those that just need to get up and running quickly, File is available for downloaded from Github. As of this article’s posting, File can only read, write, and delete files, but more capabilities will be coming soon.

Let me know if you have any questions or suggestions below. If you found a bug in the article please post a comment.

Photo by Fredy Jacob on Unsplash

--

--

Corey Davis

Hi! I'm an engineering manager and senior iOS engineer at PlugShare, and a former contributor to Kodeco. Previously , IBM, & more. https://about.me/corbindavis