Saving an Image to Swift Data

krz · June 15, 2024

In the rapidly evolving landscape of app development, managing and persisting data efficiently is crucial. Whether you are building a photo gallery app, a note-taking tool with image attachments, or any application that involves handling images, the way you store and retrieve those images can significantly impact performance and user experience.

Enter SwiftData, a powerful framework introduced by Apple to streamline data management in Swift applications. SwiftData not only simplifies the process of saving and retrieving various types of data but also integrates seamlessly with SwiftUI, allowing developers to create robust and scalable apps with minimal effort.

In this blog post, we will delve into a practical example of using SwiftData to save images selected by the user through a PhotosPicker. We will walk through the entire process, from setting up your data model to displaying a list of saved images with descriptions. By the end of this guide, you’ll have a solid understanding of how to leverage SwiftData to enhance your app’s functionality and provide a smooth user experience. Let’s get started!

Requirements for Swift Data

Using SwiftCode requires the following steps:

1. Define the class

The class has to use the @Model macro and the @Attribute(.externalStorage) to save the image. Don’t forget to import SwiftData. In our app, we want to save the image and a description of the image. The class looks like this:

import Foundation
import SwiftData
import SwiftUI

@Model
class Item {
    
    var descript: String
    
    @Attribute(.externalStorage)
    var image: Data?
    
    init(descript: String = "", image: Data? = nil) {
        self.descript = descript
        self.image = image
    }
}

2. Add a modelContainer

You need to add a modelContainer to the WindowGroup of your app:

import SwiftData
import SwiftUI

@main
struct PhotoRememberApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Item.self)
    }
}

3. Add the modelContext Environment to your ContentView

@Environment(\.modelContext) private var modelContext

The PhotosPicker

To select a Photo, we define a PhotosPicker:

                PhotosPicker(
                    selection: $selectedPhoto,
                    matching: .images,
                    photoLibrary: .shared()
                ) {
                    Text("Select a Photo")
                }
                .onChange(of: selectedPhoto) {
                    Task {
                        await loadImageData(from: selectedPhoto)
                    }
                }

Save the image

Then we build a save Button that calls a function so save this image to SwiftData:

    private func saveImage(_ descript: String, _ data: Data) {
        let newItem = Item(descript: descript, image: data)
        modelContext.insert(newItem)
        
        do {
            try modelContext.save()
        } catch {
            print("Failed to save context: \(error)")
        }
        // Reset selected photo and imageData after saving
        selectedPhoto = nil
        imageData = nil
        imageDescription = ""
    }

The complete app

Let’s put everything together:

import SwiftUI
import PhotosUI
import SwiftData


struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]
    @State private var selectedPhoto: PhotosPickerItem?
    @State private var imageData: Data?
    
    @State private var imageDescription: String = ""

    var body: some View {
        NavigationView {
            VStack {
                List(items) { item in
                    HStack {
                        if let imageData = item.image, let uiImage = UIImage(data: imageData) {
                            Image(uiImage: uiImage)
                                .resizable()
                                .scaledToFill()
                                .frame(width: 50, height: 50)
                                .clipShape(RoundedRectangle(cornerRadius: 8))
                        } else {
                            Rectangle()
                                .fill(Color.gray)
                                .frame(width: 50, height: 50)
                                .clipShape(RoundedRectangle(cornerRadius: 8))
                        }
                        Text(item.descript)
                    }
                }
                .navigationTitle("Photo Selector")

                if let imageData = imageData, let uiImage = UIImage(data: imageData) {
                    Image(uiImage: uiImage)
                        .resizable()
                        .scaledToFit()
                        .frame(height: 200)
                } else {
                    Text("No Image Selected")
                        .frame(height: 200)
                }

                PhotosPicker(
                    selection: $selectedPhoto,
                    matching: .images,
                    photoLibrary: .shared()
                ) {
                    Text("Select a Photo")
                }
                .onChange(of: selectedPhoto) {
                    Task {
                        await loadImageData(from: selectedPhoto)
                    }
                }

                if imageData != nil {
                    TextField("Enter image description", text: $imageDescription)
                                            .textFieldStyle(RoundedBorderTextFieldStyle())
                                            .padding()
                    
                    Button("Save Image") {
                        if let imageData = imageData {
                            saveImage(imageDescription, imageData)
                        }
                    }
                    .padding()
                }
            }
            .padding()
        }
    }

    private func loadImageData(from item: PhotosPickerItem?) async {
        if let data = try? await item?.loadTransferable(type: Data.self) {
            self.imageData = data
        }
    }

    private func saveImage(_ descript: String, _ data: Data) {
        let newItem = Item(descript: descript, image: data)
        modelContext.insert(newItem)
        
        do {
            try modelContext.save()
        } catch {
            print("Failed to save context: \(error)")
        }
        // Reset selected photo and imageData after saving
        selectedPhoto = nil
        imageData = nil
        imageDescription = ""
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Twitter, Facebook