Hi,
I did cloudkit synchronization using swiftdata.
However, synchronization does not occur automatically, and synchronization occurs intermittently only when the device is closed and opened.
For confirmation, after changing the data in Device 1 (saving), when the data is fetched from Device 2, there is no change.
I've heard that there's still an issue with swiftdata sync and Apple is currently troubleshooting it, is the phenomenon I'm experiencing in the current version normal?
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
I've been struggling to get a ValueTransformer to work while developing in Xcode 16 for iOS 18. Despite thinking I had everything set up correctly, I keep encountering the following error whenever I create a tag:
let tag = Tag(name: name, color: color.toPlatformColor()) // Converts it to NSColor or UIColor
modelContext.insert(tag)
SwiftData/DataUtilities.swift:184: Fatal error: Unable to determine the primitive for Attribute - name: color, options: [transformable with Optional("ColorTransformer")], valueType: UIColor, defaultValue: UIExtendedSRGBColorSpace 0 0 1 1, hashModifier: nil
Here’s what I’m dealing with:
Tag Model:
@Model
public final class Tag: Identifiable {
var name: String = ""
@Attribute(.transformable(by: ColorTransformer.self))
var color: PlatformColor = PlatformColor.blue
init(name: String, color: PlatformColor) {
self.name = name
self.color = color
}
}
ColorTransformer:
final class ColorTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
guard let color = value as? PlatformColor else { return nil }
do {
let data = try NSKeyedArchiver.archivedData(
withRootObject: color, requiringSecureCoding: true)
return data
} catch {
assertionFailure("Failed to transform `PlatformColor` to `Data`")
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return PlatformColor.black }
do {
let color = try NSKeyedUnarchiver.unarchivedObject(
ofClass: PlatformColor.self, from: data as Data)
return color
} catch {
assertionFailure("Failed to transform `Data` to `PlatformColor`")
return nil
}
}
override class func transformedValueClass() -> AnyClass {
return PlatformColor.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
public static func register() {
ValueTransformer.setValueTransformer(ColorTransformer(), forName: .colorTransformer)
}
}
extension NSValueTransformerName {
static let colorTransformer = NSValueTransformerName(rawValue: "ColorTransformer")
}
Platform Alias:
#if os(macOS)
typealias PlatformColor = NSColor
#else
typealias PlatformColor = UIColor
#endif
The ValueTransformer is registered when the ModelContainer is created at app startup:
var sharedModelContainer: ModelContainer = {
ColorTransformer.register()
// Other configurations...
}()
I've also tried not aliasing the colors to see if that changes anything, but I still encounter the same issue. Any guidance or suggestions would be greatly appreciated!
Hi,
I have two SwiftData models that are not working as intended. I'm able to add data to the models, but sometimes after refreshing the app data is missing. Any guidance would be greatly appreciated.
I am building an app where tasks can be shown on multiple selected days. The user will select a day and the available tasks should be shown. I am struggling to build a view hierarchy where the visible tasks will update when added to the currently shown day.
A simplified model looks like the below:
@Model final class Day
{
private(set) var dayID: UUID = UUID()
var show: [Item]?
}
@Model final class Item
{
private(set) var itemID: UUID = UUID()
@Relationship(inverse: \Day.show) var showDays: [Day]?
}
I have a view representing a day, and I would like it to display a list of associated tasks below.
The view doesn't update if I list a day's tasks directly, e.g. List(day.show) {}
I get a compile error if I build a sub view that queries Item using the day's show array:
let show = day.show ?? []
let predicate = #Predicate<Item> { item in
show.contains(where: { $0.itemID == item.itemID }) }
I get runtime error if I flip the predicate:
let dayID = day.dayID
let predicate: Predicate<Item> = #Predicate<Item> { item in
item.showDays.flatMap { $0.contains(where: { $0.dayID == dayID }) } ?? false }
I'm at a loss of how to approach this, other that just forcing the whole view hierarchy to update every time a make a change.
Is there a recommended approach to this situation please?
I'm wondering if there is a way to force a re-fetch of a @Query property inside of a SwiftUI view so I could offer a pull-to-refresh mechanism for users to force-refresh a view.
Why would I want this?
iOS 18.0 and 18.1 currently contain some regressions that prevent SwiftData from properly gathering model updates caused by ModelActor's running on background threads and the suggested workarounds (listening for .NSManagedObjectContextDidSave) don't work well in most scenarios and do not cause queries in the current view to be fully re-evaluated (see Importing Data into SwiftData in the Background Using ModelActor and @Query).
I am trying to get my head around SwiftData, and specifically some more "advanced" ideas that I have not seen covered in the various tutorials.
Specifically, I have a class that includes a collection that may or may not contain elements. For now I am experimenting with a simple array of Date, and I don't know if I should make it an optional, or an empty array. Without SwiftData in the mix it seems like it's probably programmers choice, but I wonder if SwiftData handles those two scenarios differently, that would suggest one over the other.
I have a working ValueTransformer that runs fine in simulator/device, but crashes in SwiftUI Preview. Even though they are the same code.
Here is my code
import Foundation
final class StringBoolDictTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
guard let stringBoolDict = value as? [String: Bool] else { return nil }
let nsDict = NSMutableDictionary()
for (key, bool) in stringBoolDict {
nsDict[key] = NSNumber(value: bool)
}
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: nsDict, requiringSecureCoding: true)
return data
} catch {
debugPrint("Unable to convert [Date: Bool] to a persistable form: \(error.localizedDescription)")
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
do {
guard let nsDict = try NSKeyedUnarchiver.unarchivedDictionary(ofKeyClass: NSString.self, objectClass: NSNumber.self, from: data) else {
return nil
}
var result = [String: Bool]()
for (key, value) in nsDict {
result[key as String] = value.boolValue
}
return result
} catch {
debugPrint("Unable to convert persisted Data to [Date: Bool]: \(error.localizedDescription)")
return nil
}
}
override class func allowsReverseTransformation() -> Bool {
true
}
override class func transformedValueClass() -> AnyClass {
NSDictionary.self
}
}
and here is the container
public struct SwiftDataManager {
public static let shared = SwiftDataManager()
public var sharedModelContainer: ModelContainer
init() {
ValueTransformer.setValueTransformer(
StringBoolDictTransformer(), forName: NSValueTransformerName("StringBoolDictTransformer")
)
let schema = Schema([,
Plan.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
sharedModelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
}
and the model
@Model
final class Plan {
@Attribute(.transformable(by: StringBoolDictTransformer.self))
var dict: [String: Bool] = [:]
}
I would get that container and pass it in appdelegate and it works fine. I would get that container and pass it inside a #Preview and it would crash with the following:
Runtime: iOS 17.5 (21F79) - DeviceType: iPhone 15 Pro
CoreFoundation:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "dict"; desired type = NSDictionary; given type = _NSInlineData; value = {length = 2, bytes = 0x7b7d}.'
libsystem_c.dylib:
abort() called
Version 16.0 (16A242d)
Hi,
I have a mac os app that I am developing. It is backed by a SwiftData database. I'm trying to set up cloudkit so that the app's data can be shared across the user's devices. However, I'm finding that every tutorial i find online makes it sound super easy, but only discusses it from the perspective of ios.
The instructions typically say:
Add the iCloud capability.
Select CloudKit from its options.
Press + to add a new CloudKit container, or select one of your existing ones.
Add the Background Modes capability.
Check the box "Remote Notifications" checkbox from its options.
I'm having issue with the following:
I don't see background modes showing up or remote notifications checkbox since i'm making a mac os app.
If i do the first 3 steps only, when i launch my app i get an app crash while trying to load the persistent store. Here is the exact error message:
Add the iCloud capability.
Select CloudKit from its options.
Press + to add a new CloudKit container, or select one of your existing ones.
Add the Background Modes capability.
Check the box "Remote Notifications" checkbox from its options.
Any help would be greatly appreciated.
var sharedModelContainer: ModelContainer = {
let schema = Schema([One.self, Two.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
The fatal error in the catch block happens when i run the app.
Many years ago I put an attribute named throws in a core data entity.
Now I want to extract the data and move it to a new swift data with more function. I try to rename the entity to avoid compile problems with throws being a swift keyword, now banned as a SwiftData field.
I need a code path to extract from the original core data using swift. The SwiftData macros seem to choke on the throws keyword and substitute blanks.
DB rename of the attribute still uses the original throws name at the code level.
I have a Query in my View that brings in some data that I then filter with a few different computed properties. I then use those properties to present the data in a view. This is the view (simplified for clarity).
struct FMListView: View {
@Query(sort: \FMList.name) var fmLists: [FMList]
private var systemTodoLists: [FMList] {
fmLists.filter { $0.ownership == Ownership.system }
}
private var userTodoLists: [FMList] {
fmLists.filter { $0.ownership == Ownership.user && $0.parentList == nil}
}
private var favoriteTodoLists: [FMList] {
fmLists.filter { $0.isFavorite }
}
var body: some View {
NavigationStack {
List {
// MARK: -- System TodoLists
ForEach(systemTodoLists) { list in
NavigationLink(value: list) {
Text(list.name)
}
}
Section(header: Text("Favorites").padding(.top, -24)) {
ForEach(favoriteTodoLists) { list in
NavigationLink(value: list) {
Text(list.name)
}
}
}
// MARK: -- User TodoLists
Section(header: Text("Lists").padding(.top, -24)) {
ForEach(fmLists.filter { $0.ownership == Ownership.user && $0.parentList == nil}) { list in
NavigationLink(value: list) {
Text(list.name)
}
}
}
}
.navigationDestination(for: FMList.self) { list in
Text(list.name)
// MARK: -- ERROR HERE
Toggle(isOn: list.isFavorite) {
Label("Favorite", systemImage: IconConstants.starIcon)
}
}
}
}
}
The challenge I have here is that I need to represent the queried data in multiple different formats (for the example, I'm just using a Text view). When I navigate to the navigationDestination I want to edit the state on a property within the model but the model in this scope isn't bindable so I can't pass it into the Toggle.
I'm not sure if the issue is my use of computed properties, or if it's my not understanding binding misusing the Toggle. I'd appreciate some guidance on this use-case - allowing me to pass a bindable version of the model down the stack once filtered on the app side.
On a somewhat related note - I am filtering with computed properties because I can't do this filter within the Query predicate. The following gave me a compiler error because the ownership.user wasn't a constant value.
$0.ownership == Ownership.user
This is what the enum looks like:
enum Ownership: String, CaseIterable, Codable {
case system = "SYSTEM"
case user = "USER"
}
I've been working with SwiftData and encountered a perplexing issue that I hope to get some insights on.
When using a @Model that has a one-to-many relationship with another @Model, I noticed that if there are multiple class variables involved, SwiftData seems to struggle with correctly associating each variable with its corresponding data.
For example, in my code, I have two models: Book and Page. The Book model has a property for a single contentPage and an optional array of pages. However, when I create a Book instance and leave the pages array as nil, iterating over pages unexpectedly returns the contentPage instead.
You can check out the code for more details here. Has anyone else faced this issue or have any suggestions on how to resolve it? Any help would be greatly appreciated!
I dont understand. How does using appended help here? I am not adding anything to the array. Here is the summary
The following code defines two SwiftData models: Book and Page. In the Book class, there is a property contentPage of type Page, and an optional array pages that holds multiple Page instances.
@Model
class Book {
var id = UUID()
var title: String
var contentPage: Page
var pages: [Page]?
init(id: UUID = UUID(), title: String, contentPage: Page) {
self.id = id
self.title = title
self.contentPage = contentPage
contentPage.book = self
}
func addPage(page: Page) {
if pages == nil {
pages = []
}
page.book = self
pages?.append(page)
}
}
enum PageType: String, Codable {
case contentsPage = "Contents"
case picturePage = "Picture"
case textPage = "Text"
case blankPage = "Blank"
}
@Model
class Page {
var id = UUID()
var pageType: PageType
var pageNumber: Int
var content: String
var book: Book?
init(id: UUID = UUID(), pageType: PageType, content: String, pageNumber: Int) {
self.id = id
self.pageType = pageType
self.pageNumber = pageNumber
self.content = content
}
}
Observed Behavior:
With the code above, I created a Book instance and populated all fields except for the pages, which was left as nil. However, when I attempt to iterate over the pages, I receive the contentPage instead. This indicates that there may be an issue with how SwiftData handles these associations.
Expected behavior - when iterating over pages I should not see contentPage since it is a separate property
Is this Relationship correct? Does this cause a circular reference? This runs on 18 but crashes on 17 in the swift data internals.
@Model
final class Item {
@Attribute(.unique) var id: UUID
var date: Date
@Relationship(deleteRule: .nullify, inverse: \Summary.item) var summary: Summary?
init(date: Date = Date.now) {
self.id = UUID()
self.date = Calendar.current.startOfDay(for: date)
self.summary = Summary(self)
}
}
@Model
final class Summary {
@Attribute(.unique) var id = UUID()
@Relationship var item: Item?
init(_ item: Item) {
self.item = item
}
}
Hello, I've a question about performance when trying to render lots of items coming from SwiftData via a @Query on a SwiftUI List. Here's my setup:
// Item.swift:
@Model final class Item: Identifiable {
var timestamp: Date
var isOptionA: Bool
init() {
self.timestamp = Date()
self.isOptionA = Bool.random()
}
}
// Menu.swift
enum Menu: String, CaseIterable, Hashable, Identifiable {
var id: String { rawValue }
case optionA
case optionB
case all
var predicate: Predicate<Item> {
switch self {
case .optionA: return #Predicate { $0.isOptionA }
case .optionB: return #Predicate { !$0.isOptionA }
case .all: return #Predicate { _ in true }
}
}
}
// SlowData.swift
@main
struct SlowDataApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([Item.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
return try! ModelContainer(for: schema, configurations: [modelConfiguration])
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
// ContentView.swift
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@State var selection: Menu? = .optionA
var body: some View {
NavigationSplitView {
List(Menu.allCases, selection: $selection) { menu in
Text(menu.rawValue).tag(menu)
}
} detail: {
DemoListView(selectedMenu: $selection)
}.onAppear {
// Do this just once
// (0..<15_000).forEach { index in
// let item = Item()
// modelContext.insert(item)
// }
}
}
}
// DemoListView.swift
struct DemoListView: View {
@Binding var selectedMenu: Menu?
@Query private var items: [Item]
init(selectedMenu: Binding<Menu?>) {
self._selectedMenu = selectedMenu
self._items = Query(filter: selectedMenu.wrappedValue?.predicate,
sort: \.timestamp)
}
var body: some View {
// Option 1: touching `items` = slow!
List(items) { item in
Text(item.timestamp.description)
}
// Option 2: Not touching `items` = fast!
// List {
// Text("Not accessing `items` here")
// }
.navigationTitle(selectedMenu?.rawValue ?? "N/A")
}
}
When I use Option 1 on DemoListView, there's a noticeable delay on the navigation. If I use Option 2, there's none. This happens both on Debug builds and Release builds, just FYI because on Xcode 16 Debug builds seem to be slower than expected: https://indieweb.social/@curtclifton/113273571392595819
I've profiled it and the SwiftData fetches seem blazing fast, the Hang occurs when accessing the items property from the List. Is there anything I'm overlooking or it's just as fast as it can be right now?
I am seeing a strange warning pop up in my SwiftData ModelActor:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
This warning is triggered by the try self.modelContext.save() call in the following function in my ModelActor:
public func purgeLocalEvents(for calendarId: PersistentIdentifier) async {
do {
let calendars = try self.modelContext.fetch(CalendarFetchDescriptors.getCalendar(calendarId))
if let calendar = calendars.first {
if let events = calendar.events {
for event in events {
self.modelContext.delete(event)
}
}
calendar.lastSync = .distantPast
try self.modelContext.save()
}
} catch {
debugPrint("Error loading calendar for event purge", error.localizedDescription)
}
}
The function in the ModelActor is called like this:
Task.detached(priority: .userInitiated) {
let actor = await RemoteGoogleCalendarActor(modelContainer: SwiftDataCoordinator.shared.fullContainer)
await actor.purgeLocalEvents(for: calendarId)
}
I perform saves from modelactors in many other places, and I've never seen this warning before. What could be causing this issue?
Can vector embeddings be used in a SwiftData Model?
If yes, are there resources available to learn more about it? or at least a guided step on how to make it work?
A fairly simple ModelContainer:
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Model1.self,
Model2.self,
Model3.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false, cloudKitDatabase: .automatic)
do {
let container = try ModelContainer(for: schema, migrationPlan: MigrationPlan.self, configurations: [modelConfiguration])
return container
} catch {
fatalError("Error: Could not create ModelContainer: \(error)")
}
}()
After upgrading to macOS 15 and disabling/enabling iCloud for the app the sync stopped working on Mac. The steps:
Go to System Settings > Apple Account > iCloud > Saved to iCloud > See all
find the App and disable iCloud. After this synced items are removed from the app and some errors thrown in the console ('..unable to initialize without an iCloud account...')
Re-enable the iCloud setting
This error appears in the console:
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](612): <NSCloudKitMirroringDelegate: 0x6000020dc1e0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134415 "(null)"
On macOS Sonoma the items are synced back to the app and the sync is restored, but on Sequoia they don't come back and the sync is not working.
I tried resetting the container, deleting all data - no help.
Submitted FB15455847
I'm seeing these errors in the console when calling ModelContainer(for:migrationPlan:configurations) for iOS 18:
error: Attempting to retrieve an NSManagedObjectModel version checksum while the model is still editable. This may result in an unstable verison checksum. Add model to NSPersistentStoreCoordinator and try again.
CoreData: error: Attempting to retrieve an NSManagedObjectModel version checksum while the model is still editable. This may result in an unstable verison checksum. Add model to NSPersistentStoreCoordinator and try again.
Is this anything to be concerned about?
(Side note: "version" is misspelled in "verison checksum")
Currently, I am planning to add a new feature to my app that allows multiple users to collaboratively manage a single legder. Initially, I chose SwiftData with iCloud for development, so I wanted to inquire whether SwiftData currently supports data sharing among multiple users through iCloud. If it does not, should I transition entirely to Core Data, or is it feasible to allow Core Data and SwiftData to work together?
Presently, I am encountering an issue with SwiftData. For instance, I have a SwiftData class Ledger that encompasses an array of SingleTransaction, which is also a SwiftData class.
Here is the question: when I save a Ledger, how can I discern that the .NSManagedObjectContextDidSave notification was triggered by saving the Ledger and not by saving a SingleTransaction? This distinction is crucial to circumvent unnecessary updates. I attempted the following syntax, but Xcode indicates that Cast from NSManagedObject to unrelated type Ledger always fails.
List {...}
.onReceive(
NotificationCenter
.default
.publisher(for: .NSManagedObjectContextDidSave)
.receive(on: DispatchQueue.main),
perform: { notification in
if let userInfo = notification.userInfo,
let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject> {
if updatedObjects.contains(where: { $0 is Ledger }) {
fetchLedgers()
}
}
}
)
What can I do?
Dear community !!!
I'm brand new in SwiftUI development. I created an app, I was almost at the end with fine tuning when I got weird behaviours with the iOS 18 version when using Swift Data.
I think I'm part of the problem but I can not figure out how to solve it.
I've searched, spent so many hours and feel a bit disappointed not succeeding.
Here is my first problem :
I've two models :
@Model
class Song: Codable {
var uuid: UUID = UUID()
var text: String = ""
var creationDate: Date = Date.now
var updatingDate: Date = Date.now
var status: Int = 0
var nbRead: Int = 0
var speed: Float = 0.5
var language: String = ""
enum CodingKeys: CodingKey {
case uuid, text, creationDate, updatingDate, status, nbRead, speed, language
}
@Relationship(inverse: \Genre.songs)
var genres: [Genre]?
...
}
and
@Model
class Genre {
//var uuid: UUID
var name: String = ""
var color: String = "Red"
var songs: [Song]?
init( name: String, color: String) {
//self.uuid = uuid
self.name = name
self.color = color
}
}
I want to list all songs organised by sections :
import SwiftData
import SwiftUI
struct SongsListView: View {
private var searchingText: String
@Environment(\.modelContext) private var modelContext
@Query(sort: \Genre.name) private var genres: [Genre]
@Query var songs : [Song]
init(searchText: String)
{
if !searchText.isEmpty {
let predicate = #Predicate<Song> { song in
song.text.localizedStandardContains(searchText)
}
_songs = Query(filter: predicate)
}
searchingText = searchText
}
var body: some View {
HStack{
List{
if !songs.isEmpty {
ForEach(genres, id: \.name){ genre in
Section(header: Text(genre.name)){
ForEach (songs){song in
if let songGenres = song.genres {
if songGenres.contains(genre){
NavigationLink {
SongView(song: song)
} label: {
Text(song.text)
}
}
}
}
}
}
.onDelete { indexSet in
indexSet.forEach { index in
let song = songs[index]
modelContext.delete(song)
}
}
Section(header: Text("Without Genre")) {
ForEach (songs){song in
if let songGenres = song.genres {
if songGenres.isEmpty{
NavigationLink {
SongView(song: song)
} label: {
Text(song.text)
}
}
}
}
.onDelete { indexSet in
indexSet.forEach { index in
let song = songs[index]
modelContext.delete(song)
}
}
}
}
}
.scrollContentBackground(.hidden)
}
}
}
On iOS 17, creating a new song adds it directly to the list in the Without Genre section.
On iOS 18, it takes around 30 seconds to be added.
I did a video, and I have a demo project to illustrate if necessary.
Thanks a lot for any hint, advice !