The title of this post is a bit misleading, because you should not store images in the user's defaults database. That is not what it was designed for. With this post, I hope I can convince other developers to use a different solution for storing images in the application's sandbox.
What Is the Problem?
The defaults system is designed for storing small pieces of data, such as settings and user preferences. It is not designed for storing large blobs of data, such as images.
Is It Possible?
Is it possible to store an image in the user's defaults database? It is, but it isn't possible to store an image as is in the user's defaults database. The defaults system only supports strings, numbers, Date
objects, and Data
objects. This means that you need to convert the image to a Data
object before you can store it in the user's defaults database. But as I mentioned earlier, I strongly discourage you from taking this approach.
Other Options
A much better solution is storing the image in the application's sandbox and only storing the location of the image in the user's defaults database. Let me show you how this works. In this example, we work with a UIImage
instance. This also works with other types, such as NSImage
.
We add an import statement for the UIKit framework and load an image with name landscape. This is just an example to illustrate the concept.
import UIKit
// Load Image
let image = UIImage(named: "landscape")
We then convert the UIImage
instance to a Data
object by invoking the pngData()
method on the UIImage
instance. Because this operation can fail, we safely unwrap the result of the pngData()
method using an if
statement.
import UIKit
// Load Image
let image = UIImage(named: "landscape")
// Convert to Data
if let data = image?.pngData() {
}
The next step is deciding where the image data will be written to. In this example, we write the image data to the Documents directory in the application's sandbox. We ask the FileManager
class for the URL of the Documents directory and append the name of the file, landscape.png, to the URL.
import UIKit
// Load Image
let image = UIImage(named: "landscape")
// Convert to Data
if let data = image?.pngData() {
// Create URL
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = documents.appendingPathComponent("landscape.png")
}
Writing a Data
object to disk is a throwing operation so we wrap it in a do-catch
statement. If the operation is successful, we store the URL in the user's defaults database.
import UIKit
// Load Image
let image = UIImage(named: "landscape")
// Convert to Data
if let data = image?.pngData() {
// Create URL
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = documents.appendingPathComponent("landscape.png")
do {
// Write to Disk
try data.write(to: url)
// Store URL in User Defaults
UserDefaults.standard.set(url, forKey: "background")
} catch {
print("Unable to Write Data to Disk (\(error))")
}
}
Should You Use User Defaults?
In this example, you could argue that it isn't necessary to store the URL of the image data in the user's defaults database and that is a valid point. If you're storing the user's profile image in the Documents directory and you always name it profile.png, then there's no need to store the URL of the image data in the user's defaults database.
What I want you to remember is that you should not use the user's defaults database for storing large blobs of data, including image data. I understand that it's convenient, but it may impact the performance of your application.
On tvOS, the system terminates your application if the size of the user's defaults database exceeds 1MB and your application is notified when it exceeds 512kB. This clearly indicates that Apple doesn't want you to misuse the user's defaults database for storing big chunks of data.