1

I'm trying to preload an app with read-only data contained in a default.store file located in the app's bundle.

import SwiftUI
import SwiftData

@main
struct AppDemo: App {
    enum Schema: VersionedSchema {
        static var versionIdentifier: SwiftData.Schema.Version = .init(1, 0, 0)
        static var models: [any PersistentModel.Type] = [Item.self]
    }
    
    let container: ModelContainer

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
    }
    
    init() {
        do {
            let url = Bundle.main.url(forResource: "default", withExtension: "store")!
            let schema = SwiftData.Schema(versionedSchema: Schema.self)
            let configuration = ModelConfiguration(url: url)
            container = try ModelContainer(for: schema, configurations: configuration)
        } catch {
            debugPrint(error)
        }
    }
}

I've used the approach shown in this example.

For reasons I don't know, it works perfectly in the simulator (iOS 17.0). But the same code fails when run on a device (iOS 17.0, still).

Unresolved error loading container Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={sourceURL=file:///private/var/containers/Bundle/Application/[UUID]/AppDemo.app/default.store, reason=Cannot migrate store in-place: error during SQL execution : attempt to write a readonly database, destinationURL=file:///private/var/containers/Bundle/Application/[UUID]/AppDemo.app/default.store, NSUnderlyingError=0x281c7ae20 {Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={NSSQLiteErrorDomain=8, NSFilePath=/private/var/containers/Bundle/Application/[UUID]/AppDemo.app/default.store, NSUnderlyingException=error during SQL execution : attempt to write a readonly database, reason=error during SQL execution : attempt to write a readonly database}}}

CoreData: Attempt to add read-only file at path file:///private/var/containers/Bundle/Application/.../AppDemo.app/default.store read/write. Adding it read-only instead. This will be a hard error in the future; you must specify the NSReadOnlyPersistentStoreOption.

How to configure the ModelContainer as read-only?

2
  • Just a shot in the dark: perhaps try passing allowsSave: false in ModelConfiguration.init.
    – Sweeper
    Commented Nov 2, 2023 at 7:15
  • @Sweeper I tried it just now, but unfortunately the error persists.
    – parapote
    Commented Nov 2, 2023 at 7:17

1 Answer 1

2

The error message indicates that the CoreData is attempting to add a read-only file as read/write. This results in a failure because the file is within your application's bundle, which is read-only. The application's bundle cannot be modified once the app has been installed, so any attempt to write to this location will fail.

You need to copy the default.store file from the bundle to the application's Documents directory (which is read/write) during the first launch of the app. Then, you can use this copied file for your ModelContainer.

init() {
    do {
        guard let bundleURL = Bundle.main.url(forResource: "default", withExtension: "store") else {
            fatalError("Failed to find default.store in app bundle")
        }

        let fileManager = FileManager.default
        let documentDirectoryURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
        let documentURL = documentDirectoryURL.appendingPathComponent("default.store")

        // Only copy the store from the bundle to the Documents directory if it doesn't exist
        if !fileManager.fileExists(atPath: documentURL.path) {
            try fileManager.copyItem(at: bundleURL, to: documentURL)
        }

        let schema = SwiftData.Schema(versionedSchema: Schema.self)
        let configuration = ModelConfiguration(url: documentURL)
        container = try ModelContainer(for: schema, configurations: configuration)
    } catch {
        debugPrint(error)
    }
}

W first get the URL of the default.store file in the app bundle. Then, we get the URL of the Documents directory and append "default.store" to it to get the destination URL. If the default.store file doesn't already exist in the Documents directory, we copy it from the bundle to the Documents directory. Finally, we use the default.store file in the Documents directory to create the ModelContainer.

2
  • 1
    Thanks for your help, this solution works. However, it has one drawback: as the store is copied, it doubles the size of the app. If no other solution is proposed, I'll accept the answer.
    – parapote
    Commented Nov 3, 2023 at 5:18
  • Core Data supports read-only stores, and you can configure this using the NSReadOnlyPersistentStoreOption option. However, this is not directly available in the SwiftData framework you're using, as it abstracts the underlying Core Data functionality.
    – Kozydot
    Commented Nov 3, 2023 at 5:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.