Post

Replies

Boosts

Views

Activity

Migrating an existing SwiftData store (explicit SQLite URL) to an App Group with CloudKit after migrating from Core Data
I have a production app that originally used Core Data + CloudKit and was later migrated to SwiftData. The SwiftData migration preserved the existing SQLite store by explicitly pointing ModelConfiguration at the original database: let configuration = ModelConfiguration(url: storeURL) let container = try ModelContainer(for: schema, configurations: configuration) Because of this, my app does not use the higher-level ModelConfiguration(groupContainer:cloudKitDatabase:) initializer. I would now like to migrate the store into an App Group so it can be shared with widgets. During the WWDC26 SwiftData Group Lab (around 18:53), the guidance was: Moving to an App Group container is more involved: it's a different directory and entitlements can't be aligned to the old location, so you'll get a new container and must copy the existing data over into the group container, then start from there. However, I couldn't find documentation describing how Apple recommends performing that copy for a SwiftData application that already uses an explicit SQLite URL. Why the Core Data APIs don't seem applicable The obvious approach would be to use Core Data APIs such as: replacePersistentStore migratePersistentStore However, these APIs require a Core Data stack and a managed object model (.momd). After migrating completely to SwiftData, I no longer have a .momd in my project, so creating an NSPersistentContainer solely to move an existing SQLite store doesn't appear to be possible. Is there a supported way to use these APIs with a SwiftData store, or are they no longer intended for this scenario? Experiment Since the migration happens before creating the ModelContainer, I experimented with simply moving the entire persistence package using FileManager before SwiftData is initialized. Specifically I move: Store.sqlite Store.sqlite-wal Store.sqlite-shm .Store_SUPPORT Store_ckAssets from the application's Application Support directory into the App Group container, and then initialize SwiftData using: let configuration = ModelConfiguration(url: appGroupStoreURL) let container = try ModelContainer(for: schema, configurations: configuration) After doing this: all existing data is present; new data can be created successfully; if I run an older build that still points to Application Support, SwiftData simply creates a brand-new empty store there, which suggests the original store was indeed moved successfully. So from a local persistence perspective, this appears to work. Remaining concern Although this approach appears to preserve the SQLite store unchanged, I don't know whether it is actually safe for CloudKit. Specifically: Does moving the complete persistence package with FileManager preserve all CloudKit metadata needed for continued synchronization? Is there any risk that CloudKit will treat the moved store as a different store and re-upload or duplicate records? Are there additional files or directories that must also be moved besides: Store.sqlite Store.sqlite-wal Store.sqlite-shm .Store_SUPPORT Store_ckAssets Is there an Apple-recommended migration path for this scenario that avoids introducing a temporary Core Data model purely to move the store? In other words: What is the recommended migration path for an existing production SwiftData application using an explicit SQLite URL to move into an App Group while continuing to use CloudKit? One additional question The SwiftData documentation provides two different ways to configure persistent storage: ModelConfiguration( groupContainer: ..., cloudKitDatabase: ... ) and ModelConfiguration( url: ..., cloudKitDatabase: ... ) My understanding is that when using the groupContainer initializer, SwiftData may automatically handle moving the persistent store into the App Group when the application is updated. However, when using the url initializer, the application is explicitly responsible for choosing the store location. Is that understanding correct? If so: Is there any supported automatic migration mechanism when using ModelConfiguration(url:), or is manual migration expected? If manual migration is expected, is moving the complete persistence package (.sqlite, -wal, -shm, .Store_SUPPORT, Store_ckAssets) before creating the ModelContainer the recommended approach? Or is there another Apple-recommended migration path for this scenario? My related posts/questions https://developer.apple.com/forums/thread/769835 https://developer.apple.com/forums/thread/769676
1
0
102
3h
How can I transition to SwiftData while migrating the persistent store to an app group container with Core Data?
I have a Core Data app on the App Store that places its persistent store in the default location within the Application Support directory. At some point, I add widgets to the app while utilizing SwiftData. I create a widget extension and the app group to link both targets. I migrate my persistent store from the default location to the app group container directory. Now, the SwiftData code pointing to the shared container in the widget extension can query data. Here's the sample project for illustration up to this point. Then, I decided to get rid of the Core Data code entirely. Move everything in the main target to SwiftData. I need to keep my persistence controller object, however, because I can never assume that the entirety of my user base has migrated. I do need to ditch the Core Data model file. Otherwise, my new SwiftData models would conflict with the auto-generated ones from that file. But once I do that, my migration code breaks. I can't set up a persistent container without the model. And I need the container for migration. SwiftData has no native interface to migrate the store to a new location. There is a line in the documentation stating that the framework copies the store to an app group container automatically. But I wasn't able to verify that claim.
0
1
496
Nov ’24
Does SwiftData copy the Core Data store to the app group container automatically?
While reading the developer documentation article Adopting SwiftData for a Core Data App, one particular line piqued my interest. For apps that evolve from a version that doesn’t have any app group container to a version that has one, SwiftData copies the existing store to the app group container. Given how troublesome it has been to migrate the Core Data persistent store to an app group container, I decided to try this out myself. I created an Xcode project using the default Core Data template. I then added a few Item objects with timestamps. There, I had what we would consider a regular Core Data app. I then created a widget extension for this app since this is one of the most common uses for adopting an app group in an Xcode project. After that, I linked the main target with the widget extension using an app group. In the widget extension, I tried to fetch the Item objects. I utilized the SwiftData code in the sample project associated with the article above. struct Provider: TimelineProvider { private let modelContainer: ModelContainer init() { let appGroupContainerID = "group.com.genebogdanovich.CoreDataSwiftDataAppGroup" guard let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupContainerID) else { fatalError("Shared file container could not be created.") } let url = appGroupContainer.appendingPathComponent("CoreDataSwiftDataAppGroup.sqlite") print("\(url)") do { modelContainer = try ModelContainer(for: Item.self, configurations: ModelConfiguration(url: url)) } catch { fatalError("Failed to create the model container: \(error)") } } } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { Task { @MainActor in let fetchDescriptor = FetchDescriptor<Item>() let items: [Item] = try! modelContainer.mainContext.fetch(fetchDescriptor) print(items) let entry = SimpleEntry(date: .now, emoji: "😀", count: items.count) let timeline = Timeline(entries: [entry], policy: .never) completion(timeline) } } The fetch yielded no results. However, as I explored the app group directory in the file system, I found a .sqlite file. That is interesting because SwiftData creates .store files by default. So, I am guessing that SwiftData did copy something. Or the ModelContainer initializer just created another empty SQLite file since the fetch returned zero results. I would highly appreciate someone elaborating on that quote from the documentation.
0
2
505
Nov ’24
Swift Charts: chartScrollTargetBehavior fails to snap to a day unit
I am working on a scrollable chart that displays days on the horizontal axis. As the user scrolls, I always want them to be able to snap to a specific day. I implemented the following steps described in this WWDC23 session to achieve this. I have set the chartScrollTargetBehavior to .valueAligned(matching: DateComponents(hour: 0)) I have set the x value unit on the BarMark to Calendar.Component.day I ended up with the chart code that looks like this: Chart(dates, id: \.self) { date in BarMark( x: .value("Date", date, unit: Calendar.Component.day), y: .value("Number", 1) ) .annotation { Text(date.formatted(.dateTime.day())) .font(.caption2) } } .chartXAxis { AxisMarks(format: .dateTime.day()) } .chartScrollableAxes(.horizontal) .chartScrollTargetBehavior(.valueAligned(matching: DateComponents(hour: 0))) .chartXVisibleDomain(length: fifteenDays) .chartScrollPosition(x: $selection) However, this fails to work reliably. There is often a situation where the chart scroll position lands on, for instance, Oct 20, 11:56 PM, but the chart snaps to Oct 21. I attempted to solve this problem by introducing an intermediate binding between a state value and a chart selection. This binding aims to normalize the selection always to be the first moment of any given date. But this hasn't been successful. private var selectionBinding: Binding<Date> { Binding { Calendar.current.startOfDay(for: selection) } set: { newValue in self.selection = Calendar.current.startOfDay(for: newValue) } } It's also worth mentioning that this issue also exists in Apple's sample project on Swift Charts. How would you approach solving this? How can I find a way to make the chart scroll position blind to time values and only recognize whole days? Here's the minimal reproducible example project for your reference.
1
1
648
Nov ’24
Migrating an existing SwiftData store (explicit SQLite URL) to an App Group with CloudKit after migrating from Core Data
I have a production app that originally used Core Data + CloudKit and was later migrated to SwiftData. The SwiftData migration preserved the existing SQLite store by explicitly pointing ModelConfiguration at the original database: let configuration = ModelConfiguration(url: storeURL) let container = try ModelContainer(for: schema, configurations: configuration) Because of this, my app does not use the higher-level ModelConfiguration(groupContainer:cloudKitDatabase:) initializer. I would now like to migrate the store into an App Group so it can be shared with widgets. During the WWDC26 SwiftData Group Lab (around 18:53), the guidance was: Moving to an App Group container is more involved: it's a different directory and entitlements can't be aligned to the old location, so you'll get a new container and must copy the existing data over into the group container, then start from there. However, I couldn't find documentation describing how Apple recommends performing that copy for a SwiftData application that already uses an explicit SQLite URL. Why the Core Data APIs don't seem applicable The obvious approach would be to use Core Data APIs such as: replacePersistentStore migratePersistentStore However, these APIs require a Core Data stack and a managed object model (.momd). After migrating completely to SwiftData, I no longer have a .momd in my project, so creating an NSPersistentContainer solely to move an existing SQLite store doesn't appear to be possible. Is there a supported way to use these APIs with a SwiftData store, or are they no longer intended for this scenario? Experiment Since the migration happens before creating the ModelContainer, I experimented with simply moving the entire persistence package using FileManager before SwiftData is initialized. Specifically I move: Store.sqlite Store.sqlite-wal Store.sqlite-shm .Store_SUPPORT Store_ckAssets from the application's Application Support directory into the App Group container, and then initialize SwiftData using: let configuration = ModelConfiguration(url: appGroupStoreURL) let container = try ModelContainer(for: schema, configurations: configuration) After doing this: all existing data is present; new data can be created successfully; if I run an older build that still points to Application Support, SwiftData simply creates a brand-new empty store there, which suggests the original store was indeed moved successfully. So from a local persistence perspective, this appears to work. Remaining concern Although this approach appears to preserve the SQLite store unchanged, I don't know whether it is actually safe for CloudKit. Specifically: Does moving the complete persistence package with FileManager preserve all CloudKit metadata needed for continued synchronization? Is there any risk that CloudKit will treat the moved store as a different store and re-upload or duplicate records? Are there additional files or directories that must also be moved besides: Store.sqlite Store.sqlite-wal Store.sqlite-shm .Store_SUPPORT Store_ckAssets Is there an Apple-recommended migration path for this scenario that avoids introducing a temporary Core Data model purely to move the store? In other words: What is the recommended migration path for an existing production SwiftData application using an explicit SQLite URL to move into an App Group while continuing to use CloudKit? One additional question The SwiftData documentation provides two different ways to configure persistent storage: ModelConfiguration( groupContainer: ..., cloudKitDatabase: ... ) and ModelConfiguration( url: ..., cloudKitDatabase: ... ) My understanding is that when using the groupContainer initializer, SwiftData may automatically handle moving the persistent store into the App Group when the application is updated. However, when using the url initializer, the application is explicitly responsible for choosing the store location. Is that understanding correct? If so: Is there any supported automatic migration mechanism when using ModelConfiguration(url:), or is manual migration expected? If manual migration is expected, is moving the complete persistence package (.sqlite, -wal, -shm, .Store_SUPPORT, Store_ckAssets) before creating the ModelContainer the recommended approach? Or is there another Apple-recommended migration path for this scenario? My related posts/questions https://developer.apple.com/forums/thread/769835 https://developer.apple.com/forums/thread/769676
Replies
1
Boosts
0
Views
102
Activity
3h
How can I transition to SwiftData while migrating the persistent store to an app group container with Core Data?
I have a Core Data app on the App Store that places its persistent store in the default location within the Application Support directory. At some point, I add widgets to the app while utilizing SwiftData. I create a widget extension and the app group to link both targets. I migrate my persistent store from the default location to the app group container directory. Now, the SwiftData code pointing to the shared container in the widget extension can query data. Here's the sample project for illustration up to this point. Then, I decided to get rid of the Core Data code entirely. Move everything in the main target to SwiftData. I need to keep my persistence controller object, however, because I can never assume that the entirety of my user base has migrated. I do need to ditch the Core Data model file. Otherwise, my new SwiftData models would conflict with the auto-generated ones from that file. But once I do that, my migration code breaks. I can't set up a persistent container without the model. And I need the container for migration. SwiftData has no native interface to migrate the store to a new location. There is a line in the documentation stating that the framework copies the store to an app group container automatically. But I wasn't able to verify that claim.
Replies
0
Boosts
1
Views
496
Activity
Nov ’24
Does SwiftData copy the Core Data store to the app group container automatically?
While reading the developer documentation article Adopting SwiftData for a Core Data App, one particular line piqued my interest. For apps that evolve from a version that doesn’t have any app group container to a version that has one, SwiftData copies the existing store to the app group container. Given how troublesome it has been to migrate the Core Data persistent store to an app group container, I decided to try this out myself. I created an Xcode project using the default Core Data template. I then added a few Item objects with timestamps. There, I had what we would consider a regular Core Data app. I then created a widget extension for this app since this is one of the most common uses for adopting an app group in an Xcode project. After that, I linked the main target with the widget extension using an app group. In the widget extension, I tried to fetch the Item objects. I utilized the SwiftData code in the sample project associated with the article above. struct Provider: TimelineProvider { private let modelContainer: ModelContainer init() { let appGroupContainerID = "group.com.genebogdanovich.CoreDataSwiftDataAppGroup" guard let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupContainerID) else { fatalError("Shared file container could not be created.") } let url = appGroupContainer.appendingPathComponent("CoreDataSwiftDataAppGroup.sqlite") print("\(url)") do { modelContainer = try ModelContainer(for: Item.self, configurations: ModelConfiguration(url: url)) } catch { fatalError("Failed to create the model container: \(error)") } } } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { Task { @MainActor in let fetchDescriptor = FetchDescriptor<Item>() let items: [Item] = try! modelContainer.mainContext.fetch(fetchDescriptor) print(items) let entry = SimpleEntry(date: .now, emoji: "😀", count: items.count) let timeline = Timeline(entries: [entry], policy: .never) completion(timeline) } } The fetch yielded no results. However, as I explored the app group directory in the file system, I found a .sqlite file. That is interesting because SwiftData creates .store files by default. So, I am guessing that SwiftData did copy something. Or the ModelContainer initializer just created another empty SQLite file since the fetch returned zero results. I would highly appreciate someone elaborating on that quote from the documentation.
Replies
0
Boosts
2
Views
505
Activity
Nov ’24
Swift Charts: chartScrollTargetBehavior fails to snap to a day unit
I am working on a scrollable chart that displays days on the horizontal axis. As the user scrolls, I always want them to be able to snap to a specific day. I implemented the following steps described in this WWDC23 session to achieve this. I have set the chartScrollTargetBehavior to .valueAligned(matching: DateComponents(hour: 0)) I have set the x value unit on the BarMark to Calendar.Component.day I ended up with the chart code that looks like this: Chart(dates, id: \.self) { date in BarMark( x: .value("Date", date, unit: Calendar.Component.day), y: .value("Number", 1) ) .annotation { Text(date.formatted(.dateTime.day())) .font(.caption2) } } .chartXAxis { AxisMarks(format: .dateTime.day()) } .chartScrollableAxes(.horizontal) .chartScrollTargetBehavior(.valueAligned(matching: DateComponents(hour: 0))) .chartXVisibleDomain(length: fifteenDays) .chartScrollPosition(x: $selection) However, this fails to work reliably. There is often a situation where the chart scroll position lands on, for instance, Oct 20, 11:56 PM, but the chart snaps to Oct 21. I attempted to solve this problem by introducing an intermediate binding between a state value and a chart selection. This binding aims to normalize the selection always to be the first moment of any given date. But this hasn't been successful. private var selectionBinding: Binding<Date> { Binding { Calendar.current.startOfDay(for: selection) } set: { newValue in self.selection = Calendar.current.startOfDay(for: newValue) } } It's also worth mentioning that this issue also exists in Apple's sample project on Swift Charts. How would you approach solving this? How can I find a way to make the chart scroll position blind to time values and only recognize whole days? Here's the minimal reproducible example project for your reference.
Replies
1
Boosts
1
Views
648
Activity
Nov ’24