Swift Data Images
You can pretty easily add images to your Swift Data apps. (Or any other data type, like text files, etc.)
I worked on a Swift Data document app, and thought, "Neat, the Sqlite db is in the directory and I'm all set." Or was I?
Like many, I'd set up a @Model
with properties like this (most elided):
@Model
public class Item: Codable {
var name: String // formal item name
var url: URL?
@Attribute(.externalStorage)
var photo: Data? // keeping it simple with one photo
}
The question then becomes: where is that .externalStorage
?
Turns out, in a document-based app, it's not within the document unless the image is sufficiently small that it's stored in the database. So let's look at how we can determine that.
(Side note: If you're going to look at your db file with a Sqlite editor, I highly recommend you make a copy of your document file and use the Sqlite editor on the copy, not the original. For this purpose, I use DB Browser for Sqlite, but anything similar works fine.
Additionally, the document file is actually a directory, so you'll want to right click on the file and select "Show Package Contents".)
Image Data Stored Externally
In the Sqlite file, you'll find your table names uppercased with a Z prepended, so Item
becomes ZITEM
. Similarly, all the column names have a prepended Z, so photo
becomes ZPHOTO
.
Note that this blob field's data starts with a hex 02
then is followed by a C string (null terminated) of hex characters in ASCII, with groups separated by dashes.
The amazing thing here is there was a file stored with that particular image, but the name of the enclosing parent directory bore no resemblance to the directory name stored in the database (02F50BB1-3202-46B9-BD64-63953D626D5E
). The app works and displays the image, but there's some level of magic mapping between the two that was completely opaque to me.
In my home directory's Library
folder, there is a Containers
directory with a subdirectory for my app, and in that container's Data/Library/Caches
there's a promise directory called com.apple.SwiftUI.filePromises-1B839980-3D13-484F-AD70-7CBB616B1156
. Inside that, the original file, with the original filename, is stored. Whereas my app's document file is in ~/Documents
, completely unrelated to my Library
directory.
If you, like me, realize a bit too late that you need to store your data in the database, that means it's going to be painful to piece together what from the Caches
directory goes where. (I seem to have lost about 30 images in the process, but fortunately was able to retrieve them all from elsewhere.)
(That container is important, because that's where your app's migrations run, and only after the migrations succeed do they migrate your document app's document. This prevents breaking your saved data, which is a Good Thing.)
Image Data Stored in Database
So, let's look at the other case: if your image (or other data) is sufficiently small to be stored in the blob field.

In this case, the first two bytes, 01
, indicate the file is stored within the remainder of the field. The remainder of the field is the raw JPEG image data, for which the file format is described here. The second and third bytes are FF
and D8
, exactly what one would expect for the start of a JPEG image, and the field ends with the correct FF
and D9
per the spec. (In other words, unlike the C string stored in the other case, there's no null terminator end character.)
If you wanted to use a raw SQL query to get the image data out of that field (to save to an external file, for example), you'd just have to remember to lop off the first two bytes in order tohave a valid JPEG to save.
The Other Possible Value
The only other possible value, given our photo
field being an optional, is NULL
.
If You Want More Control
Let's say you want more control, and you want to write your images within the Document's directory, but still keep the data in Sqlite as is.
While SwiftData is quite fussy about touching the database (e.g., opening it up with a Sqlite viewer or editor), it's absolutely possible to store images within the same document. You'd just need to have a more complete FileDocument
implementation than the simple DocumentGroup that's the default for SwiftData SwiftUI apps:
DocumentGroup(editing: .myDocument, migrationPlan: MyMigrationPlan.self) {
ContentView()
}
Some of what's needed to be done is covered in this article by Paul Hudson, but that doesn't cover the nuances of adding additional file saves into your FileDocument
class, just adding a FileDocument
into your DocumentGroup
.
Here are some places to look for ideas, though:
- Kaleidoscope has some variations on FileDocument.
- GrabShot has some interesting image-related manipulation. However, the comments are in Cyrillic (likely Russian, given the dev's location).
- PlantUML4iPad is for UML documents, but it does cover saving a slightly more complex document type here.
- Speculid is several years old, but functions to manage multiple image assets, and this is one of the most complete
FileDocument
implementations I've seen. - GRDBDocumentBasedAppExample shows using a
ReferenceFileDocument
with aFileWrapper
and some complexities that are specific to GRDB (which is aSwiftData
replacement). May be useful withpointfreeco's
sharing-grdb project (I haven't checked that, though).
Then there's the question of when to save the data. If you've got automatic saving going on, you'll need some sort of post-commit hook to also force the image save. Additionally, you'll then be responsible for cleaning up old files if, say, the person drops a different image/file and now you've got the previously stored image as well as the new one.
Photo by Alexander Nrjwolf on Unsplash
Tagged with: