I have been working on an ios application which I decided to use the new SwiftData architecture, and I now have realized that SwiftData does not support public or shared databases using SwiftData.
I am a new and upcoming Swift developer, who has been self learning the Apple Swift technology. I just learned in this forum that Swift Data does not support Public and Shared Data and I also understand that no plans that have been announced to addressed this feature in the IOS 18 release time frame.
The use case for my application is to implement a social type application, something like applications including X, facebook, etc. These type of applications appear to use Public, Shared and Private data in various existing Apple application.
I would like to complete an application using Swift using ICloud, but I must assume that these current social applications are using other technologies. I am assuming you can do this in Core Data, but my understanding is Apple is asking that we use Swift Data to replace core data. I also am assuming that Swift data is built on core data technology layers. So ... to me it seems hopeful, somehow, to accomplish this using IOS 17 and follow the recommend Swift Data Path.
What are my options for completing these data objectives in IOS? I hope I am addressing these questions in the proper and best forum? Much thanks for any suggestions.
SwiftData
RSS for tagSwiftData is an all-new framework for managing data within your apps. Models are described using regular Swift code, without the need for custom editors.
Posts under SwiftData tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Target
tvOS 17.4
macOS Version
14.5
Xcode Version
15.4 (15F31d)
I am working on integrating SwiftData (not migrating from CoreData) into an app in active development for tvOS, and I cannot get the project to compile whenever the @Model macro is introduced.
I've traced every error back to the code generated by the @Model, and commenting it out removes the errors. I am able to use @Model on a fresh project, but any attempt to import SwiftData results in the build failing with the following errors:
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModel0D0fMe_.swift:1:1 Type 'DeviceModel' does not conform to protocol 'Observable'
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModelC2id18_PersistedPropertyfMa_.swift:4:9 Member 'setValue' cannot be used on value of type 'any BackingData<DeviceModel>'; consider using a generic constraint instead
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModel0D0fMe_.swift:4:36 'Observable' is not a member type of struct 'AeolusKit.Observation'
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModelC2id18_PersistedPropertyfMa_.swift:4:50 Cannot convert value of type 'String' to expected argument type 'PersistentIdentifier'
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModelC2id18_PersistedPropertyfMa_.swift:8:54 Cannot infer key path type from context; consider explicitly specifying a root type
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModelC2id18_PersistedPropertyfMa_.swift:12:64 Cannot infer key path type from context; consider explicitly specifying a root type
/var/folders/t5/c9qp7_4j19x9s3p3z26kn6v80000gn/T/swift-generated-sources/@__swiftmacro_9AeolusKit11DeviceModel0D0fMm_.swift:25:50 Type 'Observation' has no member 'ObservationRegistrar'
the code in question causing the error:
import Foundation
import SwiftData
@Model
public final class DeviceModel {
@Attribute(.unique) public var id: String
init(id: String) {
self.id = id
}
}
I've already done the following:
clean the project
erase the contents of the derived directory
restart xcode
restart my mac.
I want to emphasize, I do not believe it is my code causing this issue, as commenting out the @Model result's in a perfectly normal build with no warnings or errors
Any help is appreciated
As I am new to programming in Swift, I have a question about SwiftData if you are writing a program and created your model and done some testing and realize you need to add something to your model that you missed during the initial setup do you have to start a whole new project? As I noticed when adding in something new to the model the project can't find the original SwiftData? Thread 1: Fatal error: failed to find a currently active container for User
I'm trying to filter results to get all characters that are associated with a story. When navigating to the view it filters as it should, but as soon as you select the Add Character option, the app just freezes.
I've found that if I comment out the Query line in the init, and filter out as part of the foreach I don't get the freeze.
Can anyone advise how I should be doing these predicates as it seems init isn't the best place to do it
The views code is
import SwiftUI
import SwiftData
struct CharacterListView: View {
let story : Story
@Query(sort: \Character.name, order: .forward) private var characters: [Character]
var body: some View {
List {
ForEach(characters) { char in
CharacterListCellView(char: char)
}
}
.toolbar {
ToolbarItem {
NavigationLink(destination: CharacterAddView(story: story)) {
Label("Add Character", systemImage: "plus")
}
}
}
.navigationBarTitle("Characters", displayMode: .inline)
}
init(story: Story) {
self.story = story
let id = story.persistentModelID
let predicate = #Predicate<Character> { char in
char.story?.persistentModelID == id
}
_characters = Query(filter: predicate, sort: [SortDescriptor(\.name)] )
}
}
I've narrowed it down to a line in the AddCharacterView file
struct CharacterAddView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) var dismiss
let story : Story
// Character details
@State private var characterName: String = ""
// Images
@State private var showImageMenu = false;
@State private var isShowPhotoLibrary = false
@State private var isShowCamera = false
//@State private var image = UIImage() <-- This line
@State private var imageChanged = false
@State private var isSaving : Bool = false
var body: some View {
}
}
If I do either of the below it works
If I comment out the UIImage variable then I don't get the freeze, but I can't update the image variable
If I comment out the query in init, then it works but doesn't filter and I have to do it as part of the foreach
I'm currently working on SwiftData, and having trouble with id properties.
I was constantly getting this error with my @Model.
error: Row (pk = 1) for entity 'AnalyzedMessage' is missing mandatory text data for property 'id'
Timestamp: 2024-05-31 14:13:24.483647+09:00 | Library: CoreData | Subsystem: com.apple.coredata | Category: error
\
@Model
final class AnalyzedMessage {
var id: String
var user: User
var createdAt: Date
var text: String
var attachments: [Attachment]
var recording: Recording?
var replyMessage: ReplyMessage?
}
As I remove all properties named id (including custom Value types), error just disappeared and app works as I intended with no issue .
@Model
final class AnalyzedMessage {
var messageID: String
var user: User
var createdAt: Date
var text: String
var recording: Recording?
}
I thought using property named id causes problem only when fetching some data like this: context.model(for: myModel.id).
So, is property id taken by @Model and forbidden to use?
If so, I think there should be a safe-guard for miss-usage just like in my case.
+) However, lots of open-source libraries use property id for their models.
Do we have to handle all of these models to bypass not to use it?
I think it's pretty disturbing..
I have an app that uses CoreData and I want to migrate to SwiftData. After following the Migrate to SwiftData session, I only need to point to my old Core Data file to read the old data and convert it to the new SwiftData format.
My question is how do I do this? Maybe worth mentioning is that my NSPersistentContainer(name: "Model") is different to my app name.
Possible Solution?
According to a Tweet by Donny Wals this is done this way:
By default a SwiftData ModelContainer will create its underlying storage in a file called default.store. If you want to change this so you can use an existing Core Data SQLite file, you can point your container to that file instead:
// point to your old sqlite file
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
modelContainer = try ModelContainer(for: [
Movie.self
], config)
My Tested Code
@main
struct SwiftData_TestApp: App {
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
let modelContainer = try ModelContainer(for: [
Item.self
], config)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}
The problem here is that I don’t get it to work in the main app struct. When using this the way described in Dive deeper into SwiftData (at 6:58) I only get the error: Cannot use instance member 'url' within property initializer; property initializers run before 'self' is available
PS: There seems to be an issue with this WWDC session method anyway – see this post.
I had a simple class called Entry with one field (timestamp: Date) and I tried to add a new field without having to uninstall the app , so I would not crash.
I tried implementing the MigrationPlan stuff but I do not know if I have to manually add a value to the new field or how exactly it works.
Here is my code.
When I use the EntrySchemaV0 it works fine but if I try to use the V1 it crashes. (could not fetch ModelContainer)
I also tried it with a custom migration stage. Crashes with error (SwiftData/BackingData.swift:432: Fatal error: Expected only Arrays for Relationships - String)
import Foundation
import SwiftData
enum EntrySchemaV0: VersionedSchema {
static var versionIdentifier = Schema.Version(0, 1, 0)
static var models: [any PersistentModel.Type] {
[Entry.self]
}
@Model
final class Entry {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
}
enum EntrySchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(0, 2, 0)
static var models: [any PersistentModel.Type] {
[Entry.self]
}
@Model
final class Entry {
var name: String
var timestamp: Date
init(name: String = "", timestamp: Date = .now) {
self.name = name
self.timestamp = timestamp
}
}
}
enum EntryMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[EntrySchemaV0.self, EntrySchemaV1.self]
}
static var stages: [MigrationStage] {
[migrateV0toV1]
}
static let migrateV0toV1 = MigrationStage.lightweight(
fromVersion: EntrySchemaV0.self,
toVersion: EntrySchemaV1.self
)
}
Hello,
im trying to parse a JSON Request into a Swift Data Model. The specific bug happens with the nutriscore.
It works perfectly fine if I do my request and Decode or Encode the Model directly. But when im trying to add it to the Database the field is empty.
Somehow when im iterating through each product (my model) and console log it, it works. But only if there is one product in my database. sometimes when im adding another product, it somehow deletes(?) all nutriscores and the console log prints "nil" for all products. even for the product it worked before.
This right here is the way I insert into my database which should be perfectly fine. And the printing works also perfectly fine and it always displays the correct nutriscore
func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
for item in addedItems {
if case let .barcode(barcode) = item {
performAPIRequest(with: barcode.payloadStringValue!) { result in
switch result {
case .success(let product):
...
var descriptor: FetchDescriptor<Product> {
var descriptor = FetchDescriptor<Product>(
predicate: #Predicate { $0.code == product.code }
)
descriptor.fetchLimit = 1
return descriptor
}
var products: [Product] = []
products = try! self.parent.context.fetch(descriptor)
print(product.nutriscore ?? "no nutri"); //WORKS PERFECTLY FINE!!
if let existingProduct = products.first {
existingProduct.amount! += 1
existingProduct.lastScannedAt = Date()
} else {
self.parent.context.insert(product)
}
self.processMeals()
case .failure(let error):
print("Error: \(error)")
}
}
}
}
}
This has to be a SwiftData Bug, or why doesn't it work
I modified the default Items example from xcode to include two models, a view with a query and a detail view. On this detail view I cannot properly move all items, only the top two, please see the attached video.
Especially strange is that some dragging does work and some does not. It changes with the number of sub-items on a todo. The models are SwiftData and it is have a one-to-many relationship. On the many relationship the problem exists.
How should the code be adjusted to make sure all items are draggable?
https://imgur.com/a/n1y7iXX
Below is all code necessary for the minimal example. I target iOS 17.5 and this shows on both preview, simulator and my iPhone.
Models
@Model
final class ToDo {
var timestamp: Date
var items: [Item]
init(timestamp: Date) {
self.timestamp = timestamp
self.items = []
}
}
@Model
final class Item {
var timestamp: Date
var done: Bool
init(timestamp: Date, done: Bool) {
self.timestamp = timestamp
self.done = done
}
}
ItemListView (Here is the problem!)
struct ItemListView: View {
@Bindable var todo: ToDo
var body: some View {
List {
ForEach($todo.items) { $item in
Text(item.timestamp.description)
}
.onMove { indexSet, offset in
todo.items.move(fromOffsets: indexSet, toOffset: offset)
}
}
}
}
ContentView
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query var items: [ToDo]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
NavigationLink {
ItemListView(todo: item)
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = ToDo(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
APP
@main
struct ListProjectApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
ToDo.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(ToDoContainer.create())
}
}
actor ToDoContainer {
@MainActor
static func create() -> ModelContainer {
let schema = Schema([
ToDo.self,
Item.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
let container = try! ModelContainer(for: schema, configurations: [modelConfiguration])
let todo = ToDo(timestamp: Date())
container.mainContext.insert(todo)
let item1 = Item(timestamp: Date(), done: false)
let item2 = Item(timestamp: Date(), done: false)
let item3 = Item(timestamp: Date(), done: false)
todo.items.append(item1)
todo.items.append(item2)
todo.items.append(item3)
return container
}
}
In my application, I query with @ModelActor. My goal in doing this is to perform the query operation in the background thread. I use .task(priority:) to display the queries. I want to use withAnimation inside the task, but as you know, it cannot be used with asynchronous functions. I'm waiting for your comments and ideas on how to do it. Thank you.
The context is to create a model instance in SwitfData and return it.
This model as a unique attribute (defined by @Attribute(.unique)). The application runs fine the first time but on the second run, it fails with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP).
What the reason behind this crash?
Here is my sample application to duplicate the issue. The issue is reproducible on MacOS and iOS:
import SwiftUI
import SwiftData
@available(iOS 17, *)
@available(macOS 11, *)
@Model
public final class MyModel : CustomStringConvertible {
@Attribute(.unique) var uuid: UUID
init(uuid: UUID) throws {
self.uuid = uuid
}
public var description: String {
return self.uuid.uuidString
}
}
@available(iOS 17, *)
@available(macOS 11, *)
@ModelActor
public actor LocalDatabaseService {
public static let shared = LocalDatabaseService()
let schema = Schema([MyModel.self])
public init() {
self.modelContainer = try! ModelContainer(for: self.schema)
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
public func createMyModel(uuid: UUID) throws -> MyModel {
let myModel = try MyModel(uuid: uuid)
let modelContext = self.modelContext
modelContext.insert(myModel)
try modelContext.save()
return myModel
}
}
struct ContentView: View {
var body: some View {
Task {
let id = UUID(uuidString: "9C66CA5B-D91C-480F-B02C-2D14EEB49902")!
let myModel = try await LocalDatabaseService.shared.createMyModel(uuid: id)
print("myModel:\(myModel)")
print("DONE")
}
return VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
return ContentView()
}
Note: a workaround is to returned the fetched model instance return try self.getMyModel(uuid: uuid):
func getMyModel(uuid: UUID) throws -> MyModel {
let fetchDescriptor = FetchDescriptor<MyModel>(predicate: #Predicate { $0.uuid == uuid })
let serverList = try modelContext.fetch(fetchDescriptor)
if !serverList.isEmpty {
if let first = serverList.first {
return first
}
}
fatalError("Could not find MyModel with uuid \(uuid)")
}
... but it does not explain the crash.
For a kind of podcast player I need to periodically update a swiftData object to keep track of the listening progress. (Happy to hear if there are better ways) I need to do this in many places in my app so I wanted to extract the modelContext into a Singleton so I can write a global function that starts the timer.
In doing so I stumbled upon a problem: The memory used by my app is steadily increasing and the device is turning hot.
@Observable
class Helper {
static let shared = Helper()
var modelContext: ModelContext?
}
@main
struct SingletontestApp: App {
let modelContainer: ModelContainer
init() {
do {
modelContainer = try ModelContainer(
for: Item.self, Item.self
)
} catch {
fatalError("Could not initialize ModelContainer")
}
Helper.shared.modelContext = modelContainer.mainContext
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}
struct ContentView: View {
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem {
Button(action: updateItemPeriodically) {
Label("Change random", systemImage: "dice")
}
}
}
} detail: {
Text("Select an item")
}
}
func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
Helper.shared.modelContext!.insert(newItem)
}
}
@MainActor
func updateItemPeriodically() { // Doesn't matter if run as global or local func
let descriptor = FetchDescriptor<Item>(sortBy: [SortDescriptor(\.timestamp)])
let results = (try? Helper.shared.modelContext?.fetch(descriptor)) ?? []
let element = results.randomElement()
let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in // Smaller time intervals worsen the problem
element?.timestamp = Date.now
}
}
}
Calling save() manually or automatically in the timer does not have any effect. I am not sure about my general way of keeping track of listening process so if you think there is a better way, feel free to correct me.
Thanks for your help
Hi,
I'm about to adopt .externalStorage but I will also use CloudKit in the near future. I did not find any reference that list of requirements for SwiftData models to be used with CloudKit, only for Core Data models.
It seems those Core Data requirements (like no-unique) apply to SwiftData as well. However I did not find any info on this:
Can @Attribute(.externalStorage) be used when I want to sync my model with CloudKit?
Here is my current code:
@Model
final public class ServerModel {
@Attribute(.unique) var serverUrl: URL
init(serverUrl: URL) {
self.serverUrl = serverUrl
}
}
@ModelActor
public actor MyDatabaseService {
public static var shared = MyDatabaseService()
public init() {
self.modelContainer = try! ModelContainer(for: ServerModel.self)
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
public func listServers() throws -> [ServerModel] {
let descriptor = FetchDescriptor<ServerModel>()
return try modelContext.fetch(descriptor)
}
}
When try to call try await MyDatabaseService.shared.listServers()
There are two problems:
ServerModel is not Sendable
"Reference to static property 'shared' is not concurrency-safe because it involves shared mutable state; this is an error in Swift 6"
For the first one, I can solve it by doing:
public struct Server {
let serverUrl: URL;
}
@Model
final public class ServerModel {
@Attribute(.unique) var serverUrl: URL
init(serverUrl: URL) {
self.serverUrl = serverUrl
}
func getSendable() -> Server {
return Server(serverUrl: self.serverUrl)
}
}
@ModelActor
public actor MyDatabaseService {
(...)
public func listServers() throws -> [Server] {
let descriptor = FetchDescriptor<ServerModel>()
return try modelContext.fetch(descriptor).map { $0.getSendable() }
}
}
I am wondering if there is a smarter solution to this first issue. SwiftData already require to define the model with basic/codable types, so if there was a magic way to get a sendable from the model.
I try to make my model 'Codable' but 'Codable' is not compatible with 'Sendable'.
For my second issue, the singleton issue. I do not really know how to fix it.
Done this model:
@Model
public final class Category: Codable {
var nom: String = ""
@Relationship(deleteRule: .cascade, inverse: \Property.category)
var properties: [Property]?
}
How to create #predicate to search text in like…
let predicate = #Predicate<Category> { category in
searchText.isEmpty || category.nom.localizedStandardContains(searchText) ||
category.properties.contains {
$0.nom.localizedStandardContains(searchText)
}
}
without this error:
SQLCore dispatchRequest: exception handling request: <NSSQLCountRequestContext: 0x6000038dc620>, to-many key not allowed here with userInfo of (null)
When I parse the query result(SwiftData) to my DetailView it works fine. But when I delete the item in the DetailView the view isn't updated.
I parse the array from the dataModel to the DetailView. But I do not understand why it's not updating when I add or delete a tree in the DetailView. Only when I append the tree to the array from the other model after insert it works.
Does someone have a tip for me?
Here is my Code: https://gist.github.com/romanindermuehle/14441c21f689e91b26942d997f75300d
Here is my models:
import SwiftData
@Model
final public class FirstModel {
let name: String
@Relationship(deleteRule: .cascade, inverse: \SecondModel.parent) var children = [SecondModel]()
init(name: String) {
self.name = name
}
}
@Model
final public class SecondModel {
let parent: FirstModel
let name: String
@Relationship(deleteRule: .cascade, inverse: \ThirdModel.parent) var children = [ThirdModel]()
init(name: String, parent: FirstModel) {
self.name = name
self.parent = parent
}
}
@Model
final public class ThirdModel {
let parent: SecondModel
let name: String
init(name: String, parent: SecondModel) {
self.name = name
self.parent = parent
}
}
Then I create my model entries:
let schema = Schema([
FirstModel.self,
SecondModel.self,
ThirdModel.self
])
let container = try ModelContainer(for: schema)
let context = ModelContext(container)
let firstModel = FirstModel(name: "my first model")
let secondModel = SecondModel(name: "my second model", parent: firstModel)
let thirdModel = ThirdModel(name: "my third model", parent: secondModel)
context.insert(firstModel)
context.insert(secondModel)
context.insert(thirdModel)
try context.save()
I want to retrieve the children from my models:
print("-- Fetch Third Model")
let thirdFetchDescriptor: FetchDescriptor<ThirdModel> = FetchDescriptor<ThirdModel>(predicate: #Predicate { $0.name == "my third model" })
let thirdModels = try context.fetch(thirdFetchDescriptor)
for entry in thirdModels {
print(">>> \(entry) - \(entry.parent) - \(entry.parent.parent)")
}
print("-- Fetch First Model")
let firstFetchDescriptor: FetchDescriptor<FirstModel> = FetchDescriptor<FirstModel>(predicate: #Predicate { $0.name == "my first model" })
let firstModels = try context.fetch(firstFetchDescriptor)
for entry in firstModels {
print(">>> \(entry) - \(entry.children)")
for child in entry.children {
print("\t>>> \(child) - \(child.children)")
}
}
... But it does not seem to work:
-- Fetch Third Model
>>> cardapart_sdk_app_ui.ThirdModel - cardapart_sdk_app_ui.SecondModel - cardapart_sdk_app_ui.FirstModel
-- Fetch First Model
>>> cardapart_sdk_app_ui.FirstModel - []
What I would expect to see:
-- Fetch First Model
>>> cardapart_sdk_app_ui.FirstModel - [cardapart_sdk_app_ui.SecondModel]
>>> cardapart_sdk_app_ui.SecondModel - [cardapart_sdk_app_ui.ThirdModel]
I am not sure what I am doing wrong or missing...
I am trying to store usdz files with SwiftData for now.
I am converting usdz to data, then storing it with SwiftData
My model
import Foundation
import SwiftData
import SwiftUI
@Model
class Item {
var name: String
@Attribute(.externalStorage)
var usdz: Data? = nil
var id: String
init(name: String, usdz: Data? = nil) {
self.id = UUID().uuidString
self.name = name
self.usdz = usdz
}
}
My function to convert usdz to data. I am currently a local usdz just to test if it is going to work.
func usdzData() -> Data? {
do {
guard let usdzURL = Bundle.main.url(forResource: "tv_retro", withExtension: "usdz") else {
fatalError("Unable to find USDZ file in the bundle.")
}
let usdzData = try Data(contentsOf: usdzURL)
return usdzData
} catch {
print("Error loading USDZ file: \(error)")
}
return nil
}
Loading the items
@Query private var items: [Item]
...
var body: some View {
...
ForEach(items) { item in
HStack {
Model3D(?????) { model in
model
.resizable()
.scaledToFit()
} placeholder: {
ProgressView()
}
}
}
...
}
How can I load the Model3D?
I have tried:
Model3D(data: item.usdz)
Gives me the errors:
Cannot convert value of type '[Item]' to expected argument type 'Binding<C>'
Generic parameter 'C' could not be inferred
Both errors are giving in the ForEach.
I am able to print the content inside item:
ForEach(items) { item in
HStack {
Text("\(item.name)")
Text("\(item.usdz)")
}
}
This above works fine for me.
The item.usdz prints something like Optional(10954341 bytes)
I would like to know 2 things:
Is this the correct way to save usdz files into SwiftData? Or should I use FileManager? If so, how should I do that?
Also how can I get the usdz from the storage (SwiftData) to my code and use it into Model3D?
import SwiftUI
import SwiftData
class DateManagerStore : ObservableObject {
@Query private var myData: [myData]
@Published var myDataToString = ""
func hopitalDataQuery() {
if let lastMyData = myData {
self.myDataToString = String(lastMyData.sorted(by: {$0.visitedDate > $1.visitedDate}).last)
}
}
}
struct MainView: View {
@EnvironmentObject var dateManagerStore : DateManagerStore
var body: some View {
VStack{
Text("\(dateManagerStore.myDataToString)")
}
.onAppear(perform: {
dateManagerStore.hopitalDataQuery()
})
}
}
I thought it would be good to manage SwiftData values used within multiple views in one place.
I wanted to use Query data in the DateManagerStore class declared as ObservableObject through onApper of the MainView.
However, when printing the myData variable within hopitalDataQuery() of the DateManagerStore class, empty data was output.
I tried to use @Query defined inside the DateManagerStore class in various ways, but none of the methods allowed me to put a value into the @Query variable 'myData'.
There is no error in Xcode itself, but no data is coming in.
I can't find any related information anywhere, so I ask if it's officially not possible.
We worked with SwiftData, and once CloudKit was integrated, the synchronization worked well. Even if I rerun the app, it works just as well.
However, when I delete the app and reinstall it, I get a Token Expired error and CloudKit doesn't work properly.
My code is organized like this
public lazy var modelContext: ModelContext = { ModelContext(modelContainer) }()
private lazy var modelContainer: ModelContainer = {
let schema = Schema([
Entity1.self,
Entity2.self,
Entity3.self,
])
let modelConfiguration = ModelConfiguration(
schema: schema,
groupContainer: .identifier("myGroupContainer"),
cloudKitDatabase: .automatic
)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
The error content is as follows
error: CoreData+CloudKit: -[PFCloudKitImportRecordsWorkItem fetchOperationFinishedWithError:completion:]_block_invoke(707): <PFCloudKitImporterZoneChangedWorkItem: 0x3022c0000 - <NSCloudKitMirroringImportRequest: 0x3036e7ac0> 1A7E53D4-E95B-423F-8887-66360F6D8865> {
(
"<CKRecordZoneID: 0x301bb1bf0; zoneName=com.apple.coredata.cloudkit.zone, ownerName=__defaultOwner__>"
)
} - Fetch finished with error:
<CKError 0x301bb5650: "Partial Failure" (2/1011); "Couldn't fetch some items when fetching changes"; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A; container ID = "MyContainerID"; partial errors: {
com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x301bb1830: "Change Token Expired" (21/2026); server message = "client knowledge differs from server knowledge"; op = 515034AC3ADC4348; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A>
}>
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _importFinishedWithResult:importer:](1390): <PFCloudKitImporter: 0x3000a1240>: Import failed with error:
<CKError 0x301bb5650: "Partial Failure" (2/1011); "Couldn't fetch some items when fetching changes"; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A; container ID = "MyContainerID"; partial errors: {
com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x301bb1830: "Change Token Expired" (21/2026); server message = "client knowledge differs from server knowledge"; op = 515034AC3ADC4348; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A>
}>
Forcing the ModelContainer to be reinitialized fixes the problem,
it's a problem to get this error in the first place,
the error doesn't even go to fatal for me, so I don't even know how to verify that it's happening.
Is there something I'm doing wrong, or do you have any good ideas for solving the same problem?