Post

Replies

Boosts

Views

Activity

SwiftData Migration: Keeps failing at the end of willMigrate
I've been trying to setup a successful migration, but it keeps failing with this error: NSCloudKitMirroringDelegate are not reusable and should have a lifecycle tied to a given instance of NSPersistentStore. I can't find any information about this online. I added breakpoints throughout the code in willMigrate, and it originally failed on this line: try? context.save() I removed that, and it still failed. After I reload the app, it doesn't run the migration again and the app loads successfully. I figured since it crashed, it would keep trying, but I guess not. Here's how my migration is setup. enum MigrationV1ToV2: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] } static var stages: [MigrationStage] { [stage] } static let stage = MigrationStage.custom( fromVersion: SchemaV1.self, toVersion: SchemaV2.self, willMigrate: { context in // Get cycles let cycles = try? context.fetch(FetchDescriptor<SchemaV1.Cycle>()) if let cycles { for cycle in cycles { // Create new recurring objects based on what's in the cycle for income in cycle.income { let recurring = SchemaV2.Recurring(name: income.name, frequency: income.frequency, kind: .income) recurring.addAmount(.init(date: cycle.startDate, amount: income.amount)) context.insert(recurring) } for expense in cycle.expenses { let recurring = SchemaV2.Recurring(name: expense.name, frequency: expense.frequency, kind: .expense) recurring.addAmount(.init(date: cycle.startDate, amount: expense.amount)) context.insert(recurring) } for savings in cycle.savings { let recurring = SchemaV2.Recurring(name: savings.name, frequency: savings.frequency, kind: .savings) recurring.addAmount(.init(date: cycle.startDate, amount: savings.amount)) context.insert(recurring) } for investment in cycle.investments { let recurring = SchemaV2.Recurring(name: investment.name, frequency: investment.frequency, kind: .investment) recurring.addAmount(.init(date: cycle.startDate, amount: investment.amount)) context.insert(recurring) } } //try? context.save() } else { print("The cycles were not able to be fetched.") } }, didMigrate: { context in // Get new recurring objects let newRecurring = try? context.fetch(FetchDescriptor<SchemaV2.Recurring>()) if let newRecurring { for recurring in newRecurring { // Get all recurring with the same name and kind let sameName = newRecurring.filter({ $0.name == recurring.name && $0.kind == recurring.kind }) // Add amount history to recurring object, and then remove matching for match in sameName { recurring.amountHistory.append(contentsOf: match.amountHistory) context.delete(match) } } //try? context.save() } else { print("The new recurring objects could not be fetched.") } } ) } Here's is my modelContainer in the app file. There is a fatal error occurring here that's crashing the app. var sharedModelContainer: ModelContainer = { let schema = Schema(versionedSchema: SchemaV2.self) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer( for: schema, migrationPlan: MigrationV1ToV2.self, configurations: [modelConfiguration] ) } catch { fatalError("Could not create ModelContainer: \(error)") } }() Does anyone have any suggestions for this? EDIT: I found this error in the console that may be relevant. BUG IN CLIENT OF CLOUDKIT: Registering a handler for a CKScheduler activity identifier that has already been registered (com.apple.coredata.cloudkit.activity.export.8F7A1261-4324-40B4-B041-886DF36FBF0A). CloudKit setup failed because it couldn't register a handler for the export activity. There is another instance of this persistent store actively syncing with CloudKit in this process. And here is the fatal error Fatal error: Could not create ModelContainer: SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer, _explanation: nil)
0
0
174
Nov ’24
SwiftData: How can I tell if a migration went through successfully?
I created 2 different schemas, and made a small change to one of them. I added a property to the model called "version". To see if the migration went through, I setup the migration plan to set version to "1.1.0" in willMigrate. In the didMigrate, I looped through the new version of Tags to check if version was set, and if not, set it. I did this incase the willMigrate didn't do what it was supposed to. The app built and ran successfully, but version was not set in the Tag I created in the app. Here's the migration: enum MigrationPlanV2: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [DataSchemaV1.self, DataSchemaV2.self] } static let stage1 = MigrationStage.custom( fromVersion: DataSchemaV1.self, toVersion: DataSchemaV2.self, willMigrate: { context in let oldTags = try? context.fetch(FetchDescriptor<DataSchemaV1.Tag>()) for old in oldTags ?? [] { let new = Tag(name: old.name, version: "Version 1.1.0") context.delete(old) context.insert(new) } try? context.save() }, didMigrate: { context in let newTags = try? context.fetch(FetchDescriptor<DataSchemaV2.Tag>()) for tag in newTags ?? []{ if tag.version == nil { tag.version = "1.1.0" } } } ) static var stages: [MigrationStage] { [stage1] } } Here's the model container: var sharedModelContainer: ModelContainer = { let schema = Schema(versionedSchema: DataSchemaV2.self) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer( for: schema, migrationPlan: MigrationPlanV2.self, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() I ran a similar test prior to this, and got the same result. It's like the code in my willMigrate isn't running. I also had print statements in there that I never saw printed to the console. I tried to check the CloudKit console for any information, but I'm having issues with that as well (separate post). Anyways, how can I confirm that my migration was successful here?
0
0
178
Nov ’24
SwiftData: Migrate from un-versioned to versioned schema
I've realized that I need to use migration plans, but those required versioned schemas. I think I've updated mine, but I wanted to confirm if this was the proper procedure. To start, none of my models were versioned. I've since wrapped them in a VersionedSchema like this: enum TagV1: VersionedSchema { static var versionIdentifier: Schema.Version = .init(1, 0, 0) static var models: [any PersistentModel.Type] { [Tag.self] } @Model final class Tag { var id = UUID() var name: String = "" // Relationships var transactions: [Transaction]? = nil init(name: String) { self.name = name } } } I also created a type alias to point to this. typealias Tag = TagV1.Tag This is what my container looks like in my app file. var sharedModelContainer: ModelContainer = { let schema = Schema([ Tag.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() The application builds and run successfully. Does this mean that my models are successfully versioned now? I'm trying to avoid an error I came across in earlier testing. That occurred because none of my models were versioned and I tried to setup a migration plan Cannot use staged migration with an unknown coordinator model version.
0
0
185
Nov ’24
SwiftData: Default value for added property
Let's say I have a model like this: @Model final class DataModel { var firstProperty: String = "" } Later on I create a new property as such: @Model final class DataModel { enum DataEnum { case dataCase } var firstProperty: String = "" var secondProperty: DataEnum? = .dataCase } My expectation is for the data that is already stored, the secondProperty would be added with a default value of .dataCase. However, it's being set to nil instead. I could have sworn it would set to the default value given to it. Has that changed, or has it always been this way? Does this require a migration plan?
0
0
166
Nov ’24
SwiftData: Failed to decode a composite attribute
I changed an enum value from this: enum Kind: String, Codable, CaseIterable { case credit } to this: enum Kind: String, Codable, CaseIterable { case credit = "Credit" } And now it fails to load the data. This is inside of a SwiftData model. I get why the error is occurring, but is there a way to resolve this issue without having to revert back or delete the data? Error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "Cannot initialize Kind from invalid String value credit", underlyingError: nil))
3
0
414
Nov ’24
MacOS SwiftUI: Back button in NavigationSplitView detail view
I have a Split View with the sidebar, content, and detail. Everything is working, but when I select on a NavigationLink in my detail view, the back button is seen next to the title above the content view. I want that back button to be displayed in the top bar on the left side of the detail view. I was expecting it to do this automatically, but I must be missing something. This is where I want it to appear. This is where it appears. I made a simplified version of my code, because it would be too much to post but this has the same behavior. struct TestView: View { enum SidebarSelections { case cycles } @State private var sidebarSelection: SidebarSelections = .cycles var body: some View { NavigationSplitView(sidebar: { List(selection: $sidebarSelection, content: { Label("Cycles", systemImage: "calendar") .tag(SidebarSelections.cycles) }) }, content: { switch sidebarSelection { case .cycles: NavigationStack { List { // Displayed in Content NavigationLink("Cycle link", destination: { // Displayed in the Detail NavigationStack { List { NavigationLink("Detail Link", destination: { Text("More details") }) } } }) } } } }, detail: { ContentUnavailableView("Nothing to see here", systemImage: "cloud") }) } } Is what I want to do possible? Here is a Stack Overflow post that had it working at one point.
3
1
377
Nov ’24
MacOS SwiftUI: Is it impossible to clear a Table selection?
I figured it would be as simple as changing the selection variable back to nil, but that seemingly has no effect. The Table row stays selected, and if I press on it again, it won't trigger my .navigationDestination(). How can I resolve this? I found another post asking the same thing, with no resolution. The only way I can clear it is by clicking with my mouse somewhere else. @State private var selectedID: UUID? = nil Table(tableData, selection: $selectedID, sortOrder: $sortOrder)... .navigationDestination(item: $selectedID, destination: { id in if let cycle = cycles.first(where: {$0.id == id}) { CycleDetailView(cycle: cycle) .onDisappear(perform: { selectedID = nil }) } })
2
0
257
Nov ’24
How to close the keyboard using SwiftUI
I've been having trouble with finding a good way to allow the user to close the keyboard. Naturally, I'd like to use the keyboard toolbar but it's so inconsistent, it's impossible. Sometimes it shows up, other times it doesn't. At the moment, it's not appearing at all no matter where I put it. On the NavigationStack, on the List inside of it, on the TextField. It just doesn't appear. I added a TapGesture to my view to set my FocusState that I'm using for the fields back to nil, however, this stops the Picker I have from working. I tried using SimultaneousGesture with TapGesture, and that didn't work. For example: .simultaneousGesture( TapGesture() .onEnded() { if field != nil { field = nil } } ) With the current setup, each TextField switches to the next TextField in the onSubmit. The last one doesn't, which will close the keyboard, but I want to give people the option to close the keyboard before that. Does anyone have a good way to close the keyboard?
0
0
232
Sep ’24
SwiftUI Charts not working in Xcode 16 Beta 2
Is there a way to workaround this issue? Can I revert back to Beta 1? Failed to build module 'Charts'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.41 clang-1600.0.24.1)', while this compiler is 'Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.11 clang-1600.0.26.2)'). Please select a toolchain which matches the SDK.
2
1
603
Sep ’24
Question about saving Data with SwiftData and CloudKit
My app is using SwiftData with CloudKit integration. Everything at the moment is working fine. I have a struct that saves Data as an optional. This is Data related to an Image. It saves and loads as expected. When I disconnect my phone from wifi or my phone network, the image still loads. I'm assuming that means the Data is being stored locally on the phone as well. Is there a way to display what's stored locally to the user inside the application? Edit: I've realized that CloudKit is saying the data is too large, but the images are still being saved. Does that mean they're only locally being saved?
1
0
321
Aug ’24
SwiftData: Unknown Relationship Type - nil
I'm getting an error: Unknown Relationship Type - nil when using SwiftData and CloudKit. I've searched for this on Google, but seems like one has experienced it before. This is on iOS 18. I have a Transaction model: @Model final class Transaction { var timestamp: Date = Date() var note: String = "" var cost: Cost? = nil var receipt: Receipt? = nil //Relationships var merchant: Merchant? = nil var category: TransactionCategory? = nil var tags: [Tag]? = nil init(timestamp: Date) { self.timestamp = timestamp } } This has a relationship with TransactionCategory. @Model final class TransactionCategory { var id = UUID() var name: String = "" //Relationship var transactions: [Transaction]? = [] init() { } init(name: String) { self.name = name } } I've tried using @Relationship here in TransactionCategory, but it didn't make a difference. There is Picker that allows you to select a Category. In this case I created a random one, which was inserted into the context and saved. This is successful by the way. When you press a done button, a Transaction is created and the Category modified like this: newTransaction.category = category. It fails as this point with that error. I also tried to create the Transaction, insert into the context, and then update the Category but it then failed at the context insertion, prior to updating the Category. As you can see, I have another model called Merchant. When you press the done button, if you've typed in a Merchant name, it will create it, insert it into the context and then update the transaction as such: newTransaction.merchant = getMerchant() private func getMerchant() -> Merchant? { //Create merchant if applicable if merchant.name.isEmpty == false { if let first = merchants.first(where: {$0.name == merchant.name.trim()}) { // Set to one that already exist return first } else { // Insert into context and insert into transction context.insert(merchant) return merchant } } return nil } This code works fine and Merchant has the same relationship with Transaction as Category does. Does anyone have any idea what could be causing this problem?
1
0
405
Jul ’24
SwiftUI and MapKit: Map camera position not updating when refreshing location
Map(initialPosition: .camera(mapCamera)) { Marker("Here", coordinate: location) } .frame(height: 300) .clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10))) .onMapCameraChange(frequency: .continuous) { cameraContext in locationManager.location = cameraContext.camera.centerCoordinate } .onReceive(locationManager.$location, perform: { location in if let location { mapCamera.centerCoordinate = location } }) class LocationDataManager: NSObject, CLLocationManagerDelegate, ObservableObject { enum LoadingState { case loading case notLoading case finished } static let shared = LocationDataManager() private let locationManager = CLLocationManager() @Published var location: CLLocationCoordinate2D? = nil @Published var loading: LoadingState = .notLoading override init() { super.init() locationManager.delegate = self } func resetLocation() { loading = .notLoading location = nil } func getLocation() { locationManager.requestLocation() loading = .loading } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { location = locations.first?.coordinate if location != nil { loading = .finished } } func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) { print("Failed to retrieve location: \(error.localizedDescription)") loading = .notLoading } } So the when the LocationButton is selected, the location is found and the marker is set correctly. You can also move the camera around to adjust the marker position, which works correctly. However, if you press the LocationButton again, it updates the marker position but it won't move the MapCamera to the new location. I can see the marker move. mapCamera.centerCoordinate = location should be doing it, but it's not. Anyone know how to fix this?
1
0
841
May ’24
SwiftUI: Keyboard toolbar item not working in iOS 17.4
It seems like this may have been an issue for a while based on what I've seen, but I have added a toolbar item to a textfield keyboard and it doesn't show. The only way I can get it to show is by opening the keyboard, typing something, closing the keyboard, and then reopening it. Anyone have a workaround for this? It's like Apple purposely wants to make it difficult to close the keyboard. TextField("Something here...", text: $text, axis: .vertical) .multilineTextAlignment(.leading) .toolbar { ToolbarItemGroup(placement: .keyboard, content: { Button("Close") { } }) }
0
5
650
May ’24
EditMode while using deleteDisabled works in one view but not the other
Here is the view in which it works struct MileageHistoryView: View { let vehicle: Vehicle init(for vehicle: Vehicle) { self.vehicle = vehicle } @Environment(\.modelContext) private var context @Environment(\.editMode) private var editMode var sorted: [Mileage] { guard let history = vehicle.mileageHistory else { return [] } return history.sorted(by: { $0.timestamp > $1.timestamp }) } var body: some View { List { ForEach(sorted) { mileage in MileageListItem(mileage, editing: Binding(get: {editMode?.wrappedValue.isEditing ?? false}, set: {_ in })) } .onDelete(perform: deleteMileage) .deleteDisabled(editMode?.wrappedValue.isEditing ?? false ? false : true) } .id(editMode?.wrappedValue.isEditing) .navigationTitle("Mileage History") .scrollContentBackground(.hidden) .toolbar { ToolbarItem(placement: .topBarTrailing, content: { EditButton() }) } } } Here is the other view where it doesn't work. In this view, it seems like when the EditButton is pressed, no change is happening with the editMode so deleteDisabled() is always set to true. struct VehiclesView: View { @Environment(\.modelContext) private var context @Environment(\.editMode) private var editMode // Local @Query private var vehicles: [Vehicle] @State private var addVehicle = false @AppStorage("vehicle-edit-alert") private var showEditAlert = true @State private var editAlert = false @State private var editShown = false var body: some View { NavigationStack { List { ForEach(vehicles) { vehicle in NavigationLink(destination: VehicleView(vehicle), label: { VehicleListItem(vehicle) }) } .onDelete(perform: deleteVehicle) .deleteDisabled(self.editMode?.wrappedValue.isEditing ?? false ? false : true) } .id(self.editMode?.wrappedValue.isEditing) .scrollContentBackground(.hidden) .navigationTitle("Vehicles") .toolbar { ToolbarItem(placement: .topBarLeading, content: { if showEditAlert && !editShown { Button("Edit") { editAlert = true } } else { EditButton() } }) ToolbarItem(placement: .topBarTrailing, content: { Button(action: { addVehicle.toggle() }, label: { Image(systemName: "plus") }) .accessibilityHint("Opens the view to add a Vehicle") }) } .fullScreenCover(isPresented: $addVehicle, content: { VehicleEditor() }) } .scrollIndicators(.hidden) } } When EditButton() is used in the second view the list item is grayed out, but the buttons to delete aren't there. Does anybody know why this is happening?
0
0
341
Apr ’24