SwiftData by Example: iOS 17 & SwiftUI 5 — Part 8

Change is inevitable so let’s see how SwiftData handles the change. Our app is looking good but it’s missing book covers so let’s add them.
We will start with new property in the book model. Since we are dealing with images, we will store them as Data.
import Foundation
import SwiftData
@Model
final class Book {
var title: String
var author: String
var publishedYear: Int
var cover: Data?This property is marked optional because we are adding a new attribute to our existing app and previously saved data should not be affected with this change as long as we are either adding new property as optional or with a default value.
Images are large and its not a good idea to store them in the data store directly(Due to performance issues), we can opt to store only the reference of the image and actual data for the image can be located somewhere else on the disk. We don’t have to do any of that manually as well, we can leverage SwiftData’s Attribute macro and define the schema configuration to store image’s binary data externally.
Update the model to include Attribute macro
import Foundation
import SwiftData
@Model
final class Book {
...
@Attribute(.externalStorage)
var cover: Data?With this change, we are ready to store book covers.
Let’s update AddNewBook view to include a photo picker so users can select book covers from their photo library.
Import PhotosUI module first.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {Add two new State properties, one to bind with photo picker and another to store the data form of selected image.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {
...
@State private var selectedCover: PhotosPickerItem?
@State private var selectedCoverData: Data?Let’s add the UI to select photos.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {
...
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
Text("Book title:")
...
HStack {
PhotosPicker(
selection: $selectedCover,
matching: .images,
photoLibrary: .shared()
) {
Label("Add Cover", systemImage: "book.closed")
}
.padding(.vertical)
Spacer()
if let selectedCoverData {
} else {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
}
}
Text("Published:")
...Build and run

Before we display the selected image, let’s get its binary data our and populate our selectedCoverData. This operation is async in nature so we will use task modifier to extract data out of the selected photo.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {
...
.navigationTitle("Add New Book")
.task(id: selectedCover) {
if let data = try? await selectedCover?.loadTransferable(type: Data.self) {
selectedCoverData = data
}
}
}
}
}Notice that we are using the loadTransferable function with the photoPickerItem. This function grants us the capability to convert the foundational data, and as photoPickerItem conforms to the Transferable protocol, it enables our model to engage in system sharing and data transfer process.
Now when we have data in our selectedCoverData, we will go back to the picker section and add the code to display selected image.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {
...
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
...
HStack {
PhotosPicker(
selection: $selectedCover,
matching: .images,
photoLibrary: .shared()
) {
Label("Add Cover", systemImage: "book.closed")
}
.padding(.vertical)
Spacer()
if let selectedCoverData,
let image = UIImage(
data: selectedCoverData) {
Image(uiImage: image)
.resizable()
.scaledToFit()
.clipShape(.rect(cornerRadius: 10))
.frame(width: 100, height: 100)
} else {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
}
}
...
.navigationTitle("Add New Book")
.task(id: selectedCover) {
if let data = try? await selectedCover?.loadTransferable(type: Data.self) {
selectedCoverData = data
}
}
}
}
}Build and run

Let’s update the save button action to include image for the book cover.
import SwiftUI
import SwiftData
import PhotosUI
struct AddNewBookView: View {
...
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
...
HStack {
...
Button("Save") {
...
book.genres = Array(selectedGenres)
if let selectedCoverData {
book.cover = selectedCoverData
}
selectedGenres.forEach { genre in
genre.books.append(book)
context.insert(genre)
}Build, run and add a new book.

With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also visit us at https://www.devtechie.com
