1

Using SwiftData and a shared SQLite database in an app and its share extension. If you merely tap Share in Safari, and the extension is shown, when you activate the app, one view throws:

DraftsView : getDrafts(): 
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=256 "The file “default.store” couldn’t be opened." UserInfo={NSFilePath=/private/var/mobile/Containers/Shared/AppGroup/455BCEA6-209A-41EA-852D-0488375BD111/Library/Application Support/default.store, NSSQLiteErrorDomain=1}



error: -executeRequest: encountered exception = I/O error for database at /private/var/mobile/Containers/Shared/AppGroup/455BCEA6-209A-41EA-852D-0488375BD111/Library/Application Support/default.store. SQLite error code:1, 'no such table: ZDRAFT' with userInfo = { NSFilePath = "/private/var/mobile/Containers/Shared/AppGroup/455BCEA6-209A-41EA-852D-0488375BD111/Library/Application Support/default.store"; NSSQLiteErrorDomain = 1; }

None of that view's items are opened. Because the view saves the model context, all those items then appear permanently deleted.

The strange thing is the main view is unaffected.

To avoid concurrency issues, there are no @Query vars in either the app or the share extension, only fetches as needed.

SwiftData model for the view which fails:

@Model final class ClassA {

    @Attribute(.unique) 
    var id: String
    
    @Relationship(deleteRule: .cascade, inverse: \ClassB.parent)
    var things: [ClassB] = [ClassB]()
}

@Model final class ClassB {
    
    @Attribute(.unique)
    var id: String
   
    // Inverse link to parent
    var parent: ClassA?

    @Relationship(deleteRule: .cascade, inverse: \ClassC.parent)
    var otherThings: [ClassC] = [ClassC]()
}

@Model final class ClassC {
    
    @Attribute(.unique)
    var id: String

    // Inverse link to parent
    var parent: ClassB?
}

For the view that's unaffected:

@Model final class Item {
    
    @Attribute(.unique)
    var id: UUID

    var itemUrl: String
}

Share extension is all one Swift class:

class ShareViewController: SLComposeServiceViewController {

    private let modelContainer: ModelContainer
    private let modelContext: ModelContext
    
    required init?(coder: NSCoder) {
        
        do {
            modelContainer = try ModelContainer(for: SavedItem.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false))
        } catch {
            fatalError("Failed to create the model container: \(error)")
        }
        
        modelContext = modelContainer.mainContext
        
        super.init(coder: coder)
    }

[…]

    func saveItem(itemUrl: String) {
        
        var fetch = FetchDescriptor<Item>(
            predicate: #Predicate { item in item.itemUrl == itemUrl }
        )

        fetch.includePendingChanges = true

        let dupes = try? modelContext.fetch(fetch)

        guard dupes?.count == 0 else { return }
            
        let id = UUID()
        let item = Item(id: id)

        modelContext.insert(item)
        
        do {
            try modelContext.save()
        } catch(let error) {
            print("error: \(error.localizedDescription)")
        }
    }
}

Tried model actors, was a crashy hairball.

If you never view the share extension icon in Safari, app works fine.

Anyone have SwiftData share extension sample code that works?

5
  • 1
    looks like you've made 2 model containers and getting a conflict on the file
    – malhal
    Commented Sep 23, 2023 at 12:29
  • @malhal don't you have to create two model containers to use SwiftData in an extension? An extension is a separate process.
    – hayesk
    Commented Sep 23, 2023 at 14:45
  • @hayesk hard to explain without knowing what the app is doing but if you look at the crash there is a model named Draft that it is expecting to find in the default.store. So it seems there is another container with other model types using the same store file as the extension is using for its single SavedItem model type.
    – malhal
    Commented Sep 23, 2023 at 14:50
  • The extension is saving a URL into a SQLite database shared with the app. The data model definition file is shared between app and extension. Thanks, hadn’t occurred to me—will try opening the existing container.
    – bobsil
    Commented Sep 24, 2023 at 0:07
  • Provisionally, adding the crashing class to the schema parameter in the init seems to have fixed it: let config = ModelConfiguration(groupContainer: .identifier("group.org.foo.appname")) modelContainer = try ModelContainer(for: ClassA.self, ClassB.self, configurations: config)
    – bobsil
    Commented Sep 24, 2023 at 23:51

1 Answer 1

1

Adding the crashing data model class to the schema parameter fixed it:

    let config = ModelConfiguration(groupContainer: .identifier("group.org.foo.appname"))
    
    modelContainer = try ModelContainer(for: ClassA.self, ClassB.self, configurations: config)

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