How to preload data into SwiftData model? – Swift

by
Ali Hasan
swift-concurrency swift-data

The Problem:

How to preload data into a SwiftData model, such as adding sample data during development or initializing the model with pre-defined values?

The Solutions:

Solution 1: Data Preloading Using @MainActors

To preload data into a SwiftData model, you can use the `@MainActors` property wrapper. This property wrapper ensures that the code block it wraps executes on the main thread, which is necessary for accessing the persistent store. The following code demonstrates how to use `@MainActors` for data preloading:

@MainActor
let appContainer: ModelContainer = {
    do {
        let container = try ModelContainer(for: Item.self)
        
        // Check if the persistent store is empty. If not, return the non-empty container.
        var itemFetchDescriptor = FetchDescriptor<Item>()
        itemFetchDescriptor.fetchLimit = 1
        
        guard try container.mainContext.fetch(itemFetchDescriptor).count == 0 else { return container }
        
        // This code will only run if the persistent store is empty.
        let items = [
            Item(timestamp: Date()),
            Item(timestamp: Date()),
            Item(timestamp: Date())
        ]
        
        for item in items {
            container.mainContext.insert(object: item)
        }
        
        return container
    } catch {
        fatalError("Failed to create container")
    }
}()

This code creates a `ModelContainer` for the `Item` model and checks if the persistent store is empty. If it’s empty, the code inserts three `Item` objects into the persistent store. The `@MainActors` property wrapper ensures that this code executes on the main thread, which is necessary for accessing the persistent store.

Solution 2: Preloading Data into SwiftData Model

To preload data into a SwiftData model, you can use the following approach:

  1. Create a static property named examples in the model class. This property should hold an array of preloaded data objects. For example:
extension Example {
    static let examples: [Example] = [
        Example(
            name: "ex1",
            type: "red"
        ),
        Example(
            name: "ex2",
            type: "blue"
        )
    ]
}
  1. In the View, you can load the preloaded data into the model context using the insert() method. This should be done in the onAppear() method of the view, only if the model context does not already contain any examples. For example:
.onAppear {
    if examples.isEmpty {
        for ex in Example.examples {
            modelContext.insert(ex)
        }
    }
}

Alternatively, you can also read the initial data from a file and load the examples into SwiftData when the app is first used. Ultimately, the specific approach you choose will depend on the requirements of your app.

Solution 3: Pre-fill store and add it to the bundle

An alternative approach is to create a pre-filled store, add it to the bundle, and work with this file. This can be a good approach if you need to insert a large amount of data.

Step 1: Insert data

Run your app and insert the data, for example:

.onAppear {
    let array = Example.examples
    guard !array.isEmpty {
        array.forEach { example in
            modelContext.insert(example)
        }
    }
}

Step 2: Navigate to Application Support folder

Navigate to the Library/Application Support folder of the simulator, for example:

/Users/nes8/Library/Developer/CoreSimulator/Devices/C975335B-C557-64D8-ABAB-2E6CAA123FFC/data/Containers/Data/Application/28EDB452-B8E8-6FBF-8C69-B48BD421442D/Library/Application Support

Step 3: Copy default.store to Xcode project

In this folder, you will see three files related to your database:

  • default.store
  • default.store-shm
  • default.store-wal

Copy default.store into your Xcode project, and remember to select ‘add to targets’.

Step 4: Initialize ModelContainer with pre-filled store

When creating a ModelContainer in your main struct App, you can initialize a ModelConfiguration with a schema and URL. To do this, get the URL of your store. Here’s a sample code snippet:

@main
struct ExampleApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Example.self,
        ])

        // get url
        let urlPath = Bundle.main.url(forResource: &quot;default&quot;, withExtension: &quot;store&quot;)!
        // load store from url
        let modelConfiguration = ModelConfiguration(schema: schema, url: urlPath)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError(&quot;Could not create ModelContainer: \(error)&quot;)
        }
    }()
...

Important Notes:

  • If you change your Models, you must repeat the steps as the schema will change.
  • Perform this procedure only during the first app launch. After that, if you modify any data, the app will create a copy of your store in the system folder (Application Support). So, after that, just load a "normal" configuration:
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

Q&A

How to preload data into SwiftData model?

You could try this approach, for example, in .onAppear() if there are no examples already load them in, as in the example code:

How to preload data into SwiftData model?

You can create a pre-filled store, add it to the bundle, and work with this file.

How to preload data into SwiftData model?

extension Example {static let examples: [Example] = [

Video Explanation:

The following video, titled "How To Preload Data Into SwiftData ModelContainer ...", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

... 55 - Creating A Custom Model Container 07:09 - Using Your Own Custom Container In SwiftData 09:02 - Pre-Filling Data On App Launch SwiftData ...