SwiftData

RSS for tag

SwiftData 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

SwiftData document based on iOS18 shows blank screen
I have some apps using SwiftData document based. They work as expected under iOS17, but not under iOS18: install the app on a iPad 11 pro (first gen) from my MacBook open the app and open an existing fils (perfect under iOS17) it shows a blank, white screen, no data I can create a new document, blank screen, no data When I open that newly created file on a iOS 17 iPad pro, it works perfect, as expected The apps were created from scratch under macOS 14.7 with the corresponding Xcode/Swift/UI version. iOS devices under iOS17 Are there any known problems with document based SwiftData-apps under iOS18, are there any changes one has to made? Thank You so much for any help!
4
0
119
46m
How to Drop Entity in SwiftData and CloudKit?
I'm using SwiftData with CloudKit and have been trying to migrate from SchemaV1 to SchemaV2, but it seems reducing the Entities crashes my app. // Example of migrating from V1 to V2 // Dropping `Person` because it's no longer needed do { // SchemaV1: Person.self, Author.self // SchemaV2: Author.self let schema = Schema(versionedSchema: SchemaV2.self) return try ModelContainer( for: schema, migrationPlan: AppSchemaMigrationPlan.self, configurations: ModelConfiguration( cloudKitDatabase: .automatic) ) } catch { fatalError("Could not create ModelContainer: \(error)") } Is it possible to drop Entities in the Schema Migration Plan? How can I delete the Person model from my Schema and CloudKit?
3
0
125
23h
SwiftData Relationship Delete Not Working (SwiftData/PersistentModel.swift:359)
SwiftData delete isn't working, when I attempt to delete a model, my app crashes and I get the following error: SwiftData/PersistentModel.swift:359: Fatal error: Cannot remove My_App.Model2 from relationship Relationship - name: model2, options: [], valueType: Model2, destination: Model2, inverseName: models3, inverseKeypath: Optional(\Model2.models3) on My_App.Model3 because an appropriate default value is not configured. I get that it's saying I don't have a default value, but why do I need one? Isn't @Relationship .cascade automatically deleting the associated models? And onto of that, why is the error occurring within the do block, shouldn't it be caught by the catch, and printed? I have put together a sample project below. import SwiftUI import SwiftData @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() .modelContainer(for: Model3.self) } } } @Model class Model1 { var name: String @Relationship(deleteRule: .cascade, inverse: \Model2.model1) var models2: [Model2] = [] init(name: String) { self.name = name } } @Model class Model2 { var name: String var model1: Model1 @Relationship(deleteRule: .cascade, inverse: \Model3.model2) var models3: [Model3] = [] init(name: String, model1: Model1) { self.name = name self.model1 = model1 } } @Model class Model3 { var name: String var model2: Model2 init(name: String, model2: Model2) { self.name = name self.model2 = model2 } } struct ContentView: View { @Query var models1: [Model1] @Environment(\.modelContext) var modelContext var body: some View { NavigationStack { List(models1) { model1 in Text(model1.name) .swipeActions { Button("Delete", systemImage: "trash", role: .destructive) { modelContext.delete(model1) do { try modelContext.save() //SwiftData/PersistentModel.swift:359: Fatal error: Cannot remove My_App.Model2 from relationship Relationship - name: model2, options: [], valueType: Model2, destination: Model2, inverseName: models3, inverseKeypath: Optional(\Model2.models3) on My_App.Model3 because an appropriate default value is not configured. } catch { print(error.localizedDescription) } } } } .toolbar { Button("Insert", systemImage: "plus") { modelContext.insert(Model3(name: "model3", model2: Model2(name: "model2", model1: Model1(name: "model1")))) } } } } }
1
0
109
18h
SwiftUI & SwiftData: Fatal Error "Duplicate keys of type" Occurs on First Launch
I'm developing a SwiftUI app using SwiftData and encountering a persistent issue: Error Message: Thread 1: Fatal error: Duplicate keys of type 'Bland' were found in a Dictionary. This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion. Details: Occurrence: The error always occurs on the first launch of the app after installation. Specifically, it happens approximately 1 minute after the app starts. Inconsistent Behavior: Despite no changes to the code or server data, the error occurs inconsistently. Data Fetching Process: I fetch data for entities (Bland, CrossZansu, and Trade) from the server using the following process: Fetch Bland and CrossZansu entities via URLSession. Insert or update these entities into the SwiftData context. The fetched data is managed as follows: func refleshBlandsData() async throws { if let blandsOnServer = try await DataModel.shared.getBlands() { await MainActor.run { blandsOnServer.forEach { blandOnServer in if let blandOnLocal = blandList.first(where: { $0.code == blandOnServer.code }) { blandOnLocal.update(serverBland: blandOnServer) } else { modelContext.insert(blandOnServer.bland) } } } } } This is a simplified version of my StockListView. The blandList is a @Query property and dynamically retrieves data from SwiftData: struct StockListView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \Bland.sname) var blandList: [Bland] @Query var users: [User] @State private var isNotLoaded = true @State private var isLoading = false @State private var loadingErrorState = "" var body: some View { NavigationStack { List { ForEach(blandList, id: \.self) { bland in NavigationLink(value: bland) { Text(bland.sname) } } } .navigationTitle("Stock List") .onAppear { doIfFirst() } } } // This function handles data loading when the app launches for the first time func doIfFirst() { if isNotLoaded { loadDataWithAnimationIfNotLoading() isNotLoaded = false } } // This function ensures data is loaded with an animation and avoids multiple triggers func loadDataWithAnimationIfNotLoading() { if !isLoading { isLoading = true Task { do { try await loadData() } catch { // Capture and store any errors during data loading loadingErrorState = "Data load failed: \(error.localizedDescription)" } isLoading = false } } } // Fetch data from the server and insert it into the SwiftData model context func loadData() async throws { if let blandsOnServer = try await DataModel.shared.getBlands() { for bland in blandsOnServer { // Avoid inserting duplicate keys by checking for existing items in blandList if !blandList.contains(where: { $0.code == bland.code }) { modelContext.insert(bland.bland) } } } } } Entity Definitions: Here are the main entities involved: Bland: @Model class Bland: Identifiable { @Attribute(.unique) var code: String var sname: String @Relationship(deleteRule: .cascade, inverse: \CrossZansu.bland) var zansuList: [CrossZansu] @Relationship(deleteRule: .cascade, inverse: \Trade.bland) var trades: [Trade] } CrossZansu: @Model class CrossZansu: Equatable { @Attribute(.unique) var id: String var bland: Bland? } Trade: @Model class Trade { @Relationship(deleteRule: .nullify) var user: User? var bland: Bland } User: class User { var id: UUID @Relationship(deleteRule: .cascade, inverse: \Trade.user) var trades: [Trade] } Observations: Error Context: The error occurs after the data is fetched and inserted into SwiftData. This suggests an issue with Hashable requirements or duplicate keys being inserted unintentionally. Concurrency Concerns: The fetch and update operations are performed in asynchronous tasks. Could this cause race conditions? Questions: Could this issue be related to how @Relationship and @Attribute(.unique) are managed in SwiftData? What are potential pitfalls with Equatable implementations (e.g., in CrossZansu) when used in SwiftData entities? Are there any recommended approaches for debugging "Duplicate keys" errors in SwiftData? Additional Info: Error Timing: The error occurs only during the app's first launch and consistently within the first minute.
1
0
126
17h
Not able to save with SwiftData. "The file “default.store” couldn’t be opened."
I get this message when trying to save my Models. CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x303034540> , I/O error for database at /var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store. SQLite error code:1, 'no such table: ZCALENDARMODEL' with userInfo of { NSFilePath = "/var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store"; NSSQLiteErrorDomain = 1; } SwiftData.DefaultStore save failed with error: Error Domain=NSCocoaErrorDomain Code=256 "The file “default.store” couldn’t be opened." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store, NSSQLiteErrorDomain=1} The App has Recipes and Calendars and the user can select a Recipe for each Calendar day. The recipe should not be referenced, it should be saved by SwiftData along with the Calendar. import SwiftUI import SwiftData enum CalendarSource: String, Codable { case created case imported } @Model class CalendarModel: Identifiable, Codable { var id: UUID = UUID() var name: String var startDate: Date var endDate: Date var recipes: [String: RecipeData] = [:] var thumbnailData: Data? var source: CalendarSource? // Computed Properties var daysBetween: Int { let days = Calendar.current.dateComponents([.day], from: startDate.midnight, to: endDate.midnight).day ?? 0 return days + 1 } var allDates: [Date] { startDate.midnight.allDates(upTo: endDate.midnight) } var thumbnailImage: Image? { if let data = thumbnailData, let uiImage = UIImage(data: data) { return Image(uiImage: uiImage) } else { return nil } } // Initializer init(name: String, startDate: Date, endDate: Date, thumbnailData: Data? = nil, source: CalendarSource? = .created) { self.name = name self.startDate = startDate self.endDate = endDate self.thumbnailData = thumbnailData self.source = source } // Convenience initializer to create a copy of an existing calendar static func copy(from calendar: CalendarModel) -> CalendarModel { let copiedCalendar = CalendarModel( name: calendar.name, startDate: calendar.startDate, endDate: calendar.endDate, thumbnailData: calendar.thumbnailData, source: calendar.source ) // Copy recipes copiedCalendar.recipes = calendar.recipes.mapValues { $0 } return copiedCalendar } // Codable Conformance private enum CodingKeys: String, CodingKey { case id, name, startDate, endDate, recipes, thumbnailData, source } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) name = try container.decode(String.self, forKey: .name) startDate = try container.decode(Date.self, forKey: .startDate) endDate = try container.decode(Date.self, forKey: .endDate) recipes = try container.decode([String: RecipeData].self, forKey: .recipes) thumbnailData = try container.decodeIfPresent(Data.self, forKey: .thumbnailData) source = try container.decodeIfPresent(CalendarSource.self, forKey: .source) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(startDate, forKey: .startDate) try container.encode(endDate, forKey: .endDate) try container.encode(recipes, forKey: .recipes) try container.encode(thumbnailData, forKey: .thumbnailData) try container.encode(source, forKey: .source) } } import SwiftUI struct RecipeData: Codable, Identifiable { var id: UUID = UUID() var name: String var ingredients: String var steps: String var thumbnailData: Data? // Computed property to convert thumbnail data to a SwiftUI Image var thumbnailImage: Image? { if let data = thumbnailData, let uiImage = UIImage(data: data) { return Image(uiImage: uiImage) } else { return nil // No image } } init(recipe: RecipeModel) { self.name = recipe.name self.ingredients = recipe.ingredients self.steps = recipe.steps self.thumbnailData = recipe.thumbnailData } } import SwiftUI import SwiftData @Model class RecipeModel: Identifiable, Codable { var id: UUID = UUID() var name: String var ingredients: String var steps: String var thumbnailData: Data? // Store the image data for the thumbnail static let fallbackSymbols = ["book.pages.fill", "carrot.fill", "fork.knife", "stove.fill"] // Computed property to convert thumbnail data to a SwiftUI Image var thumbnailImage: Image? { if let data = thumbnailData, let uiImage = UIImage(data: data) { return Image(uiImage: uiImage) } else { return nil // No image } } // MARK: - Initializer init(name: String, ingredients: String = "", steps: String = "", thumbnailData: Data? = nil) { self.name = name self.ingredients = ingredients self.steps = steps self.thumbnailData = thumbnailData } // MARK: - Copy Function func copy() -> RecipeModel { RecipeModel( name: self.name, ingredients: self.ingredients, steps: self.steps, thumbnailData: self.thumbnailData ) } // MARK: - Codable Conformance private enum CodingKeys: String, CodingKey { case id, name, ingredients, steps, thumbnailData } required init(from decoder: Decoder) throws { ... } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(ingredients, forKey: .ingredients) try container.encode(steps, forKey: .steps) try container.encode(thumbnailData, forKey: .thumbnailData) } }
1
0
156
17h
SwiftData relationshipKeyPathsForPrefetching not working
relationshipKeyPathsForPrefetching in SwiftData does not seem to work here when scrolling down the list. Why? I would like all categories to be fetched while posts are fetched - not while scrolling down the list. struct ContentView: View { var body: some View { QueryList( fetchDescriptor: withCategoriesFetchDescriptor ) } var withCategoriesFetchDescriptor: FetchDescriptor<Post> { var fetchDescriptor = FetchDescriptor<Post>() fetchDescriptor.relationshipKeyPathsForPrefetching = [\.category] return fetchDescriptor } } struct QueryList: View { @Query var posts: [Post] init(fetchDescriptor: FetchDescriptor<Post>) { _posts = Query(fetchDescriptor) } var body: some View { List(posts) { post in VStack { Text(post.title) Text(post.category?.name ?? "") .font(.footnote) } } } } @Model final class Post { var title: String var category: Category? init(title: String) { self.title = title } } @Model final class Category { var name: String init(name: String) { self.name = name } }
2
0
154
4d
RealityView Not Refreshing With SwiftData
Hi, I am trying to update what entities are visible in my RealityView. After the SwiftData set is updated, I have to restart the app for it to appear in the RealityView. Also, the RealityView does not close when I move to a different tab. It keeps everything on and tracking, leaving the model in the same location I left it. import SwiftUI import RealityKit import MountainLake import SwiftData struct RealityLakeView: View { @Environment(\.modelContext) private var context @Query private var items: [Item] var body: some View { RealityView { content in print("View Loaded") let lakeScene = try? await Entity(named: "Lake", in: mountainLakeBundle) let anchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: SIMD2<Float>(0.2, 0.2))) @MainActor func addEntity(name: String) { if let lakeEntity = lakeScene?.findEntity(named: name) { // Add the Cube_1 entity to the RealityView anchor.addChild(lakeEntity) } else { print(name + "entity not found in the Lake scene.") } } addEntity(name: "Island") for item in items { if(item.enabled) { addEntity(name: item.value) } } // Add the horizontal plane anchor to the scene content.add(anchor) content.camera = .spatialTracking } placeholder: { ProgressView() } .edgesIgnoringSafeArea(.all) } } #Preview { RealityLakeView() }
3
0
194
2d
SwiftData Query and optional relationships
Xcode 16.2 (16C5032a) FB16300857 Consider the following SwiftData model objects (only the relevant portions are shown) (note that all relationships are optional because eventually this app will use CloudKit): @Model final public class Team { public var animal: Animal? public var handlers: [Handler]? ... } @Model final public class Animal { public var callName: String public var familyName: String @Relationship(inverse: \Team.animal) public var teams: [Team]? ... } @Model final public class Handler { public var givenName: String @Relationship(inverse: \Team.handlers) public var teams: [Team]? } Now I want to display Team records in a list view, sorted by animal.familyName, animal.callName, and handlers.first.givenName. The following code crashes: struct TeamListView: View { @Query<Team>(sort: [SortDescriptor(\Team.animal?.familyName), SortDescriptor(\Team.animal?.callName), SortDescriptor(\Team.handlers?.first?.givenName)]) var teams : [Team] var body: some View { List { ForEach(teams) { team in ... } } } } However, if I remove the sort clause from the @Query and do the sort explicitly, the code appears to work (at least in preliminary testing): struct TeamListView: View { @Query<Team> var teams: [Team] var body: some View { let sortedTeams = sortResults() List { ForEach(sortedTeams) { team in ... } } } private func sortResults() -> [Team] { let results: [Team] = teams.sorted { team1, team2 in let fam1 = team1.animal?.familyName ?? "" let fam2 = team2.animal?.familyName ?? "" let comp1 = fam1.localizedCaseInsensitiveCompare(fam2) if comp1 == .orderedAscending { return true } if comp1 == .orderedDescending { return false } ... <proceed to callName and (if necessary) handler givenName comparisons> ... } } } While I obviously have a workaround, this is (in my mind) a serious weakness in the implementation of the Query macro.
5
0
204
1w
SwiftData & CloudKit: Deduplication Logic
I followed these two resources and setup history tracking in SwiftData. SwiftData History tracking: Track model changes with SwiftData history For data deduplication: Sharing Core Data objects between iCloud users In the sample app (CoreDataCloudKitShare), a uuid: UUID field is used for deduplication logic. It sorts the entities by their uuids, reserves the first one, and marks the others as deduplicated. Would it be a viable solution to use a createdAt: Date field for the sort operation instead of a uuid field, or are dates inherently problematic?
4
0
179
3d
How can I create a AppIntent with SwiftData correctly?
I currently create a AppIntent that contains a custom AppEntity, it shows like this struct GetTimerIntent: AppIntent { static let title: LocalizedStringResource = "Get Timer" @Parameter(title: "Timer") var timer: TimerEntity func perform() async throws -> some IntentResult { .result(value: timerText(timer.entity)) } static var parameterSummary: some ParameterSummary { Summary("Get time of \(\.$timer)") } func timerText(_ timer: ETimer) -> String { // Implementation Folded } } struct TimerEntity: AppEntity { var entity: ETimer static let defaultQuery: TimerQuery = .init() static var typeDisplayRepresentation: TypeDisplayRepresentation { TypeDisplayRepresentation(name: "Timer") } var id: UUID { entity.identifier } var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(entity.title)") } } To get the timers, I create a TimerQuery type to fetch them from SwiftData containers. struct TimerQuery: EntityQuery, Sendable { func entities(for identifiers: [UUID]) async throws -> [TimerEntity] { print(identifiers) let context = ModelContext(ModelMigration.sharedContainer) let descriptor = FetchDescriptor<ETimer>( predicate: #Predicate { identifiers.contains($0.identifier) }, sortBy: [.init(\.index)] ) let timers = try context.fetch(descriptor) print(timers.map(\.title)) return timers.map { TimerEntity(entity: $0) } } } Everything looks make sense since I code it. When I'm testing, the console jump No ConnectionContext found for 105553169752832 and I can't get my datas. How can I solve this issue?
1
0
179
1w
Can't batch delete with one-to-many to self relationship
I have a simple model that contains a one-to-many relationship to itself to represent a simple tree structure. It is set to cascade deletes so deleting the parent node deletes the children. Unfortunately I get an error when I try to batch delete. A test demonstrates: @Model final class TreeNode { var parent: TreeNode? @Relationship(deleteRule: .cascade, inverse: \TreeNode.parent) var children: [TreeNode] = [] init(parent: TreeNode? = nil) { self.parent = parent } } func testBatchDelete() throws { let config = ModelConfiguration(isStoredInMemoryOnly: true) let container = try ModelContainer(for: TreeNode.self, configurations: config) let context = ModelContext(container) context.autosaveEnabled = false let root = TreeNode() context.insert(root) for _ in 0..<10 { let child = TreeNode(parent: root) context.insert(child) } try context.save() // fails if first item doesn't have a nil parent, succeeds otherwise // which row is first is random, so will succeed sometimes try context.delete(model: TreeNode.self) } The error raised is: CoreData: error: Unhandled opt lock error from executeBatchDeleteRequest Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on TreeNode/parent and userInfo { NSExceptionOmitCallstacks = 1; NSLocalizedFailureReason = "Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on TreeNode/parent"; "_NSCoreDataOptimisticLockingFailureConflictsKey" = ( ); } Interestingly, if the first record when doing an unsorted query happens to be the parent node, it works correctly, so the above unit test will actually work sometimes. Now, this can be "solved" by changing the reverse relationship to an optional like so: @Relationship(deleteRule: .cascade, inverse: \TreeNode.parent) var children: [TreeNode]? The above delete will work fine. However, this causes issues with predicates that test counts in children, like for instance deleting only nodes where children is empty for example: try context.delete(model: TreeNode.self, where: #Predicate { $0.children?.isEmpty ?? true }) It ends up crashing and dumps a stacktrace to the console with: An uncaught exception was raised Keypath containing KVC aggregate where there shouldn't be one; failed to handle children.@count (the stacktrace is quite long and deep in CoreData's NSSQLGenerator) Does anyone know how to work around this?
5
0
210
1w
Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary
I get the following fatal error when the user clicks Save in AddProductionView. Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary. This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion. As far as I’m aware, SwiftData automatically makes its models conform to Hashable, so this shouldn’t be a problem. I think it has something to do with the picker, but for the life of me I can’t see what. This error occurs about 75% of the time when Save is clicked. I'm using Xcode 16.2 and iPhone SE 2nd Gen. Any help would be greatly appreciated… Here is my code: import SwiftUI import SwiftData @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() .modelContainer(for: Character.self, isAutosaveEnabled: false) } } } @Model final class Character { var name: String var production: Production var myCharacter: Bool init(name: String, production: Production, myCharacter: Bool = false) { self.name = name self.production = production self.myCharacter = myCharacter } } @Model final class Production { var name: String init(name: String) { self.name = name } } struct ContentView: View { @State private var showingSheet = false var body: some View { Button("Add", systemImage: "plus") { showingSheet.toggle() } .sheet(isPresented: $showingSheet) { AddProductionView() } } } struct AddProductionView: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) var modelContext @State var production = Production(name: "") @Query var characters: [Character] @State private var characterName: String = "" @State private var selectedCharacter: Character? var filteredCharacters: [Character] { characters.filter { $0.production == production } } var body: some View { NavigationStack { Form { Section("Details") { TextField("Title", text: $production.name) } Section("Characters") { List(filteredCharacters) { character in Text(character.name) } HStack { TextField("Character", text: $characterName) Button("Add") { let newCharacter = Character(name: characterName, production: production) modelContext.insert(newCharacter) characterName = "" } .disabled(characterName.isEmpty) } if !filteredCharacters.isEmpty { Picker("Select your role", selection: $selectedCharacter) { Text("Select") .tag(nil as Character?) ForEach(filteredCharacters) { character in Text(character.name) .tag(character as Character?) } } .pickerStyle(.menu) } } } .toolbar { Button("Save") { //Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary. This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion. if let selectedCharacter = selectedCharacter { selectedCharacter.myCharacter = true } modelContext.insert(production) do { try modelContext.save() } catch { print("Failed to save context: \(error)") } dismiss() } .disabled(production.name.isEmpty || selectedCharacter == nil) } } } }
2
0
208
17h
Can I change iOS version in package.swift to use SwiftData?
For the swift student challenge I was hoping to use swift data, I found that since it's a playground app, in package.swift the defaults is set to iOS 16 which means you can't use swift data. I changed it to iOS 17 and everything works but I want to know if that goes against the rules in anyway, changing th bios version in package.swift? This was the code I changed in package.swift. let package = Package( name: "ProStepper", platforms: [ .iOS("17.0") ],
3
1
254
1w
swiftdata model polymorphism?
I have a SwiftData model where I need to customize behavior based on the value of a property (connectorType). Here’s a simplified version of my model: @Model public final class ConnectorModel { public var connectorType: String ... func doSomethingDifferentForEveryConnectorType() { ... } } I’d like to implement doSomethingDifferentForEveryConnectorType in a way that allows the behavior to vary depending on connectorType, and I want to follow best practices for scalability and maintainability. I’ve come up with three potential solutions, each with pros and cons, and I’d love to hear your thoughts on which one makes the most sense or if there’s a better approach: **Option 1: Use switch Statements ** func doSomethingDifferentForEveryConnectorType() { switch connectorType { case "HTTP": // HTTP-specific logic case "WebSocket": // WebSocket-specific logic default: // Fallback logic } } Pros: Simple to implement and keeps the SwiftData model observable by SwiftUI without any additional wrapping. Cons: If more behaviors or methods are added, the code could become messy and harder to maintain. **Option 2: Use a Wrapper with Inheritance around swiftdata model ** @Observable class ParentConnector { var connectorModel: ConnectorModel init(connectorModel: ConnectorModel) { self.connectorModel = connectorModel } func doSomethingDifferentForEveryConnectorType() { fatalError("Not implemented") } } @Observable class HTTPConnector: ParentConnector { override func doSomethingDifferentForEveryConnectorType() { // HTTP-specific logic } } Pros: Logic for each connector type is cleanly organized in subclasses, making it easy to extend and maintain. Cons: Requires introducing additional observable classes, which could add unnecessary complexity. **Option 3: Use a @Transient class that customizes behavior ** protocol ConnectorProtocol { func doSomethingDifferentForEveryConnectorType(connectorModel: ConnectorModel) } class HTTPConnectorImplementation: ConnectorProtocol { func doSomethingDifferentForEveryConnectorType(connectorModel: ConnectorModel) { // HTTP-specific logic } } Then add this to the model: @Model public final class ConnectorModel { public var connectorType: String @Transient public var connectorImplementation: ConnectorProtocol? // Or alternatively from swiftui I could call myModel.connectorImplementation.doSomethingDifferentForEveryConnectorType() to avoid this wrapper func doSomethingDifferentForEveryConnectorType() { connectorImplementation?.doSomethingDifferentForEveryConnectorType(connectorModel: self) } } Pros: Decouples model logic from connector-specific behavior. Avoids creating additional observable classes and allows for easy extension. Cons: Requires explicitly passing the model to the protocol implementation, and setup for determining the correct implementation needs to be handled elsewhere. My Questions Which approach aligns best with SwiftData and SwiftUI best practices, especially for scalable and maintainable apps? Are there better alternatives that I haven’t considered? If Option 3 (protocol with dependency injection) is preferred, what’s the best way to a)manage the transient property 2) set the correct implementation and 3) pass reference to swiftdata model? Thanks in advance for your advice!
0
0
128
2w
SwiftData data duplication
I've got an application built on top of SwiftData (+ CloudKit) which is published to App Store. I've got a problem where on each app update, the data saved in the database is duplicated to the end user. Obviously this isn't wanted behaviour, and I'm really looking forward to fixing it. However, given the restrictions of SwiftData, I haven't found a single fix for this. The data duplication happens automatically on the first initial sync after the update. My guess is that it's because it doesn't detect the data already in the device, so it pulls all data from iCloud and appends it to the database where data in reality exists.
1
0
209
2w
SwiftData and CloudKit Development vs. Production Database
Hi, I'm working on a macOS app that utilizes SwiftData to save some user generated content to their private databases. It is not clear to me at which point the app I made starts using the production database. I assumed that if I produce a Release build that it will be using the prod db, but that doesn't seem to be the case. I made the mistake of distributing my app to users before "going to prod" with CloudKit. So after I realized what I had done, I inspected my CloudKit dashboard and records and I found the following: For my personal developer account the data is saved in the Developer database correctly and I can inspect it. When I use the "Act as iCloud account" feature and use one of my other accounts to inspect the data, I notice that for the other user, the data is neither in the Development environment nor the Production environment. Which leads me to believe it is only stored locally on that user's machine, since the app does in fact work, it's just not syncing with other devices of the same user. So, my question is: how do I "deploy to production"? I know that there is a Deploy Schema Changes button in the CloudKit dashboard. At which point should I press that? If I press it now, before distributing a new version of my app, will that somehow "signal" the already running apps on user's machines to start using the Production database? Is there a setting in Xcode that I need to check for my Release build, so that the app does in fact start using the production db? Is there a way to detect in the code whether the app is using the Production database or not? It would be useful so I can write appropriate migration logic, since I don't want to loose existing data users already have saved locally.
3
0
340
2w
SwiftData crashes after updating to MacOS 15.2
After updating to 15.2 I am seeing frequent crashes in my in-development app related to SwiftData. For instance, I have a 100% reproducible crash when I make the app lose and regain focus. There is also a crash that seem to be triggered by a modelContext.save() call in one of my ModelActors. With both of these crashes, the issue seems to be around keeping SwiftData models up to date. The first item in the stacktrace that is not machinecode is always some getter on a SwiftData collection or object. In the console, these crashes are accompanied by output along the lines of: === AttributeGraph: cycle detected through attribute 820680 === precondition failure: setting value during update: 930592 error: the replacement path doesn't exist: "/var/folders/b7/0dw7ztp13fgfxlj19by851tw0000gn/T/swift-generated-sources/@__swiftmacro_10SpaceDebug8TodoListV5todos33_5575DE008494C519BB9FA49C405133E1LL5QueryfMa_.swift" error: the replacement path doesn't exist: "/var/folders/b7/0dw7ztp13fgfxlj19by851tw0000gn/T/swift-generated-sources/@__swiftmacro_10SpaceDebug8TodoListV5todos33_5575DE008494C519BB9FA49C405133E1LL5QueryfMa_.swift" Can't show file for stack frame : <DBGLLDBStackFrame: 0x35a57c4e0> - stackNumber:27 - name:TodoList.todos.getter. The file path does not exist on the file system: /var/folders/b7/0dw7ztp13fgfxlj19by851tw0000gn/T/swift-generated-sources/@__swiftmacro_10SpaceDebug8TodoListV5todos33_5575DE008494C519BB9FA49C405133E1LL5QueryfMa_.swiftCan't show file for stack frame : <DBGLLDBStackFrame: 0x35a57c4e0> - stackNumber:27 - name:TodoList.todos.getter. The file path does not exist on the file system: /var/folders/b7/0dw7ztp13fgfxlj19by851tw0000gn/T/swift-generated-sources/@__swiftmacro_10SpaceDebug8TodoListV5todos33_5575DE008494C519BB9FA49C405133E1LL5QueryfMa_.swiftCan't show file for stack frame : <DBGLLDBStackFrame: 0x35a5a82f0> - stackNumber:62 - name:TodoList.todos.getter. The file path does not exist on the file system: /var/folders/b7/0dw7ztp13fgfxlj19by851tw0000gn/T/swift-generated-sources/@__swiftmacro_10SpaceDebug8TodoListV5todos33_5575DE008494C519BB9FA49C405133E1LL5QueryfMa_.swift Has anyone run into something similar? I'm looking for suggestions on how to debug this. Cheers, Bastiaan
2
0
252
2d
SwiftData Migration Fail: What kind of backing data is this?
I'm trying to test migration between schemas but I cannot get it to work properly. I've never been able to get a complex migration to work properly unfortunately. I've removed a property and added 2 new ones to one of my data models. This is my current plan. enum MigrationV1toV2: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] } static let migrateV1toV2 = MigrationStage.custom( fromVersion: SchemaV1.self, toVersion: SchemaV2.self, willMigrate: { context in print("Inside will migrate") // Get old months let oldMonths = try context.fetch(FetchDescriptor<SchemaV1.Month>()) print("Number of old months:\(oldMonths.count)") for oldMonth in oldMonths { // Convert to new month let newMonth = Month(name: oldMonth.name, year: oldMonth.year, limit: oldMonth.limit) print("Number of transactions in oldMonth: \(oldMonth.transactions?.count)") print("Number of transactions in newMonth: \(newMonth.transactions?.count)") // Convert transactions for transaction in oldMonth.transactions ?? [] { // Set action and direction let action = getAction(from: transaction) let direction = getDirection(from: transaction) // Update category if necessary var category: TransactionCategory? = nil if let oldCategory = transaction.category { category = TransactionCategory( name: oldCategory.name, color: SchemaV2.Category.Colors.init(rawValue: oldCategory.color?.rawValue ?? "") ?? .blue, icon: getCategoryIcon(oldIcon: oldCategory.icon) ) // Remove old category context.delete(oldCategory) } // Create new let new = Transaction( date: transaction.date, action: action, direction: direction, amount: transaction.amount, note: transaction.note, category: category, month: newMonth ) // Remove old transaction from month oldMonth.transactions?.removeAll(where: { $0.id == transaction.id }) // Delete transaction from context context.delete(transaction) // Add new transaction to new month newMonth.transactions?.append(new) } // Remove old month context.delete(oldMonth) print("After looping through transactions and deleting old month") print("Number of transactions in oldMonth: \(oldMonth.transactions?.count)") print("Number of transactions in newMonth: \(newMonth.transactions?.count)") // Insert new month context.insert(newMonth) print("Inserted new month into context") } // Save try context.save() }, didMigrate: { context in print("In did migrate") let newMonths = try context.fetch(FetchDescriptor<SchemaV2.Month>()) print("Number of new months after migration: \(newMonths.count)") } ) static var stages: [MigrationStage] { [migrateV1toV2] } } It seems to run fine until it gets the the line: try context.save(). At this point it fails with the following line: SwiftData/PersistentModel.swift:726: Fatal error: What kind of backing data is this? SwiftData._KKMDBackingData<Monthly.SchemaV1.Transaction> Anyone know what I can do about this?
1
0
198
2w
Triggering a Live Activity from a Widget-Based App Intent
Hello everyone, I have an app leveraging SwiftData, App Intents, Interactive Widgets, and a Control Center Widget. I recently added Live Activity support, and I’m using an App Intent to trigger the activity whenever the model changes. When the App Intent is called from within the app, the Live Activity is created successfully and appears on both the Lock Screen and in the Dynamic Island. However, if the same App Intent is invoked from a widget, the model is updated as expected, but no Live Activity is started. Here’s the relevant code snippet where I call the Live Activity: ` await LiveActivityManager.shared.newSessionActivity(session: session) And here’s how my attribute is defined: struct ContentState: Codable, Hashable { var session: Session } } Is there any known limitation or workaround for triggering a Live Activity when the App Intent is initiated from a widget? Any guidance or best practices would be greatly appreciated. Thank you! David
1
0
209
2w
SwiftData and async functions
Hello, I recently published an app that uses Swift Data as its primary data storage. The app uses concurrency, background threads, async await, and BLE communication. Sadly, I see my app incurs many fringe crashes, involving EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, EXC_BREAKPOINT, etc. I followed these guidelines: One ModelContainer that is stored as a global variable and used throughout. ModelContexts are created separately for each task, changes are saved manually, and models are not passed around. Threads with different ModelContexts might manipulate and/or read the same data simultaneously. I was under the impression this meets the usage requirements. I suspect perhaps the issue lies in my usage of contexts in a single await function, that might be paused and resumed on a different thread (although same execution path). Is that the case? If so, how should SwiftData be used in async scopes? Is there anything else particularly wrong in my approach?
2
0
280
2w