I'm still getting started with SwiftData and having trouble understanding how to persist a model with a relationship in a container. I've tried to distill the example below to be pretty simple.
Here are some example model definitions. I would like to be able to define a n Item. That Item can have SubItems related to it in a one-to-many fashion. Each SubItem is required to be attached to a parent Item.
final class Item {
var name: String
var subitems: [Item]?
init(
name: String,
subitems: [SubItem]? = nil
) {
self.name = name
}
}
@Model
final class SubItem {
var name: String
init(name: String) {
self.name = name
}
}
In my app I am then defining a preview container with some pre-saved data already.
Item(
name: "item1",
subitems: [
SubItem(name: "subItemA"),
SubItem(name: "subItemB")
]
),
Item(
name: "item2",
subitems: [
SubItem(name: "subItemC"),
SubItem(name: "subItemD")
]
)
]
@MainActor
let PreviewContainer: ModelContainer = {
do {
let schema = Schema([
Item.self,
SubItem.self,
])
let container = try ModelContainer(
for: schema, configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
for item in PreviewItems {
container.mainContext.insert(item)
for subItem in item.subitems! {
container.mainContext.insert(subItem)
}
}
return container
} catch {
fatalError("Failed to create container")
}
}()
My app is then defined as follows...
struct SwiftDataTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(PreviewContainer)
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
HStack {
VStack {
Text(items[0].name)
Text(String(items[0].subitems!.count))
// Text(items[0].subitems![0].name)
// Text(items[0].subitems![1].name)
}
Spacer()
VStack {
Text(items[1].name)
Text(String(items[1].subitems!.count))
// Text(items[0].subitems![0].name)
// Text(items[0].subitems![1].name)
}
}
.padding(100)
}
}
#Preview {
ContentView()
.modelContainer(PreviewContainer)
}
The preview loads without an issue, but if I uncomment the lines that access the SubItems it crashes. In the preview I can also see that each Item has 0 SubItems related to it. For some reason, the model container is not actually storing the `SubItem even though they are defined in PreviewItems.
Some things I've tried
Explicitly adding the relationship
Adding a Item property in SubItem to link back to it's parent
In the PreviewContainer definition, manually insert the SubItems as well as the parent Items
Any help is appreciated, thanks
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
Hi,
I understand how to make one to many relationship in SwiftData and how to show the child records, like all cities of a country. But how to navigate and show the parent record from a child record, Like I want to show a country of a city ?
like country.cities show all cities of a country, will cities.country work to show the country ? like cities.country.name ?
Kind Regards
Hello,
i have a route with many points
for routes:
@Model
public class Route(){
public id: UUID = UUID()
var name: String
var desc: String
var points: [Point] = []
}
@Model
public class Point(){
public id: UUID = UUID()
var speed: double
var route : Route
}
when I like to add point.
route.point.append(point)
I get all ways this error:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cc1698ec)
my Xcode version 15.3
Hi,
I saw many examples on how to insert one model object at a time in SwiftData using modelContext.insert() , but can that command insert an array of objects in one batch as well ?
--
Kind Regards
Hi all,
I'm getting a strange SwiftData error at runtime in my voice recorder app. Whenever I attempt to generate and cache a samples array so that my app can visualize the waveform of the audio, the app crashes and the following pops up in Xcode:
{
@storageRestrictions(accesses: _$backingData, initializes: _samples)
init(initialValue) {
_$backingData.setValue(forKey: \.samples, to: initialValue)
_samples = _SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.samples)
return self.getValue(forKey: \.samples)
}
set {
_$observationRegistrar.withMutation(of: self, keyPath: \.samples) {
self.setValue(forKey: \.samples, to: newValue)
}
}
}
With an execution breakpoint on the line _$observationRegistrar.withMutation(of: self, keyPath: \.samples).
Here is my model class:
import Foundation
import SwiftData
@Model final class Recording {
var id : UUID?
var name : String?
var date : Date?
var samples : [Float]? = nil
init(name: String) {
self.id = UUID()
self.name = name
self.date = Date.now
}
}
And here is where the samples are being generated (sorry for the long code):
private func processSamples(from audioFile: AVAudioFile) async throws -> [Float] {
let sampleCount = 128
let frameCount = Int(audioFile.length)
let samplesPerSegment = frameCount / sampleCount
let buffer = try createAudioBuffer(for: audioFile, frameCapacity: AVAudioFrameCount(samplesPerSegment))
let channelCount = Int(buffer.format.channelCount)
let audioData = try readAudioData(from: audioFile, into: buffer, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount)
let processedResults = try await processAudioSegments(audioData: audioData, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount)
var samples = createSamplesArray(from: processedResults, sampleCount: sampleCount)
samples = applyNoiseFloor(to: samples, noiseFloor: 0.01)
samples = normalizeSamples(samples)
return samples
}
private func createAudioBuffer(for audioFile: AVAudioFile, frameCapacity: AVAudioFrameCount) throws -> AVAudioPCMBuffer {
guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: frameCapacity) else {
throw Errors.AudioProcessingError
}
return buffer
}
private func readAudioData(from audioFile: AVAudioFile, into buffer: AVAudioPCMBuffer, sampleCount: Int, samplesPerSegment: Int, channelCount: Int) throws -> [[Float]] {
var audioData = [[Float]](repeating: [Float](repeating: 0, count: samplesPerSegment * channelCount), count: sampleCount)
for segment in 0..<sampleCount {
let segmentStart = AVAudioFramePosition(segment * samplesPerSegment)
audioFile.framePosition = segmentStart
try audioFile.read(into: buffer)
if let channelData = buffer.floatChannelData {
let dataCount = samplesPerSegment * channelCount
audioData[segment] = Array(UnsafeBufferPointer(start: channelData[0], count: dataCount))
}
}
return audioData
}
private func processAudioSegments(audioData: [[Float]], sampleCount: Int, samplesPerSegment: Int, channelCount: Int) async throws -> [(Int, Float)] {
try await withThrowingTaskGroup(of: (Int, Float).self) { taskGroup in
for segment in 0..<sampleCount {
let segmentData = audioData[segment]
taskGroup.addTask {
var rms: Float = 0
vDSP_rmsqv(segmentData, 1, &rms, vDSP_Length(samplesPerSegment * channelCount))
return (segment, rms)
}
}
var results = [(Int, Float)]()
for try await result in taskGroup {
results.append(result)
}
return results
}
}
private func createSamplesArray(from processedResults: [(Int, Float)], sampleCount: Int) -> [Float] {
var samples = [Float](repeating: 0, count: sampleCount)
vDSP_vfill([0], &samples, 1, vDSP_Length(sampleCount))
for (segment, rms) in processedResults {
samples[segment] = rms
}
return samples
}
private func applyNoiseFloor(to samples: [Float], noiseFloor: Float) -> [Float] {
var result = samples
let noiseFloorArray = [Float](repeating: noiseFloor, count: samples.count)
vDSP_vsub(noiseFloorArray, 1, samples, 1, &result, 1, vDSP_Length(samples.count))
return result
}
private func normalizeSamples(_ samples: [Float]) -> [Float] {
var result = samples
var min: Float = 0
var max: Float = 0
vDSP_minv(samples, 1, &min, vDSP_Length(samples.count))
vDSP_maxv(samples, 1, &max, vDSP_Length(samples.count))
if max > min {
var a: Float = 1.0 / (max - min)
var b: Float = -min / (max - min)
vDSP_vsmsa(samples, 1, &a, &b, &result, 1, vDSP_Length(samples.count))
} else {
vDSP_vfill([0.5], &result, 1, vDSP_Length(samples.count))
}
return result
}
And this is how the processSamples function is used:
private func loadAudioSamples() async {
let url = recording.fileURL
if let audioFile = loadAudioFile(url: url) {
if recording.samples == nil {
recording.samples = try? await processSamples(from: audioFile)
}
}
}
private func loadAudioFile(url: URL) -> AVAudioFile? {
do {
let audioFile = try AVAudioFile(forReading: url)
return audioFile
} catch {
return nil
}
}
Any help or leads would be greatly appreciated! Thanks!
I've had a persistent but hard to reliably reproduce issue when using SwiftData with SwiftUI.
Sometimes, when some properties of a SwiftData model object are used in the UI (like the title of a note in a list of notes) and something in the model is modified, the UI stops responding. The modification in the model could be a completely different object and of a different type and could be as simple as toggling a Bool, it could be performed on the main thread from inside a Button's action using the ModelContext from the environment or on a separate thread using a ModelActor. The issue even appears and disappears between builds of the same code.
As an example, this is the kind of code that leads to this issue, though I haven't been able to make a minimal reproduction because of how inconsistently it appears:
@ModelActor
struct NoteModifier {
func append() {
guard let notes = try? modelContext.fetch(FetchDescriptor<Note>()) else { return }
notes.randomElement()!.title.append("ABC")
try? modelContext.save()
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var notes: [Note]
var body: some View {
VStack {
List {
ForEach(notes) { note in
VStack {
Text(note.title)
Text(note.contents)
}
}
}
Button {
Task {
await NoteModifier(modelContainer: modelContext.container).append()
}
} label: {
Text("Bug")
}
}
}
}
When it happens and I try pausing execution, it's locked inside kevent_id in the getter for one of the properties of my model object like this:
In Instruments:
I am getting the following error:
Failed to save diary entry to CloudKit: <CKError 0x6000035adec0: "Server Rejected Request" (15/2000); op = 10F3CACEA9EC09B6; uuid = 22DDB0B8-9F1B-4BE6-A51B-2ADD08B469B1; container ID = "iCloud.com.domainname.productname">
What should I do to prevent this from happening?
I put the right container name and still this happens.
I was expecting the data model object to save in the iCloud.
In Swift Data I have a basic model
@Model class MovieSD {
@Attribute(.unique) var title: String
var genre: [String] = [String]()
init(title: String) {
self.title = title
}
}
I am trying to create predicates when fetching the data. Creating a predicate to search by title works as expected
let movieTitle = #Predicate<MovieSD> { movie in
movie.title.contains("filterstring")
}
But when attempting to do the same for the String array
let movieGenre = #Predicate<MovieSD> { movie in
movie.genre.contains("filterstring")
}
Results in a crash with EXC_BAD_ACCESS
Similar approaches produce a different error and point to the likely issue.
let movieGenre2 = #Predicate<MovieSD> { movie in
if movie.genre.contains(where: { $0 == "filterstring" }) {
return true
} else {
return false
}
}
Results in a crash with the error:
error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x281a96840> , Can't have a non-relationship collection element in a subquerySUBQUERY(genre, $$_local_1, $$_local_1 == "SciFi") with userInfo of (null)
or alternatively largely the same error for:
let movieGenre3 = #Predicate<MovieSD> { movie in
movie.genre.filter { genre in
return genre == "filterstring"
}.count > 0
}
But I couldn't seem to find an approach to create a SUBQUERY with #Predicate
Naturally, I can use similar to the above to filter the array that is returned. But this seems inefficient. And it seems it should be possible to filter
Any help showing how to filter a String array with a predicate with Swift Data would be appreciated
I'm using SwiftData to persist my items in storage. I used .modelContext to pass in my shared context, and on iOS 18 (both on a physical device and a simulator), I discovered a bug where SwiftData doesn't automatically save my data. For example, I could add a new item, go to the next screen, change something that reloads a previous screen, and SwiftData just forgets the item that I added. Please find the fully working code attached.
While writing this post, I realized that if I use .modelContainer instead of .modelContext, the issue is solved. So I have two questions:
It seems like .modelContainer is the go-to option when working with SwiftData, but why did an issue occur when I used .modelContext and passed in a shared container? When should we use .modelContext over .modelContainer?
What was the bug? It's working fine in iOS 17, but not in iOS 18. Or is this expected?
Here's the fully working code so you can copy and paste:
import SwiftUI
import SwiftData
typealias NamedColor = (color: Color, name: String)
extension Color {
init(r: Double, g: Double, b: Double) {
self.init(red: r/255, green: g/255, blue: b/255)
}
static let namedColors: [NamedColor] = [
(.blue, "Blue"),
(.red, "Red"),
(.green, "Green"),
(.orange, "Orange"),
(.yellow, "Yellow"),
(.pink, "Pink"),
(.purple, "Purple"),
(.teal, "Teal"),
(.indigo, "Indigo"),
(.brown, "Brown"),
(.cyan, "Cyan"),
(.gray, "Gray")
]
static func name(for color: Color) -> String {
return namedColors.first(where: { $0.color == color })?.name ?? "Blue"
}
static func color(for name: String) -> Color {
return namedColors.first(where: { $0.name == name })?.color ?? .blue
}
}
@main
struct SwiftDataTestApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
@AppStorage("accentColor") private var accentColorName: String = "Blue"
var body: some Scene {
WindowGroup {
NavigationStack {
HomeView()
}
.tint(Color.color(for: accentColorName))
}
.modelContainer(sharedModelContainer) // This works
// .modelContext(ModelContext(sharedModelContainer)) // This doesn't work
}
}
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
struct HomeView: View {
@State private var showSettings = false
@Environment(\.modelContext) var modelContext
@AppStorage("accentColor") private var accentColorName: String = "Blue"
@Query private var items: [Item]
var body: some View {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
Button {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
} label: {
Image(systemName: "plus")
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
}
}
.navigationTitle("Habits")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: { showSettings = true }) {
Label("", systemImage: "gearshape.fill")
}
}
}
.navigationDestination(isPresented: $showSettings) {
colorPickerView
}
}
private var colorPickerView: some View {
Form {
Section(header: Text("Accent Color")) {
Picker("Accent Color", selection: $accentColorName) {
ForEach(Color.namedColors, id: \.name) { namedColor in
Text(namedColor.name)
.tag(namedColor.name)
.foregroundColor(namedColor.color)
}
}
.pickerStyle(.wheel)
}
}
.navigationTitle("Settings")
}
}
Hi everyone,
I’m working on a SwiftUI project where I want the entire page to be scrollable. Within this page, I have a bottom section that displays a list of items, and these items are being rendered from a SwiftData model object. My main goal is to make this list reorderable using the onMove method.
The challenge I’m facing is that using a List inside a ScrollView requires a fixed height for the List, which doesn’t work for my use case because the content is dynamic and the height can vary. Since I’m using SwiftData to manage my data, I need a way to keep this list fully dynamic.
I’m open to using a VStack instead of a List to display the items, but I still need the reorder functionality that List provides. Does anyone know a good way to achieve this behavior? Is there a way to use onMove or similar functionality without needing a fixed height, or perhaps an alternative approach that might work better with SwiftData?
Any suggestions or insights would be greatly appreciated!
Thanks!
I’m writing test apps using SwiftData. Running migrations locally works fine. But I couldn’t find anything on how to handle a situation, where my local app is running on e.g. ‘MigrationSchemaV2’ and an iOS app is still running on ‘MigrationSchemaV1’ hence needs to be updated before the data migration takes place.
I expect the local migration to be synced to iCloud automatically therefore the iOS app would crash.
I’m looking for documentation, tutorials, best practice or lessons learned in order to understand the basic implementation idea, its dependencies and implications.
modelContext.fetchIdentifiers(descriptor) errors when using a SortDescriptor to sort by a variable and returns no models. The fetch works fine without a SortDescriptor, thus
FetchDescriptor<MyModel>()
works fine, but
FetchDescriptor<MyModel>(sortBy: [.init(\.title)])
or
FetchDescriptor<MyModel>(sortBy: [SortDescriptor(\.title)])
errors with console message
The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
I am using Xcode Version 16.0 beta 6 (16A5230g).
How can one observe changes in the SwiftData DB?
I'm aware that this is possible via Queries, but I need to fetch the data in background, so a query is not an option.
I'm aware of the ModelContext.didSave / .willSave notifications, but these don't work with iOS 17.
-> How can I observe changes to models of a certain type? I don't want to observe the whole database.
https://github.com/ordo-one/package-benchmark/issues/264
Hi! I am seeing this error specifically when I try to run the Ordo One benchmarks package with a SwiftData context. I am not sure if there is something missing in Ordo One or if this is some kind of legit SwiftData error. My benchmarks seem to be running fine even after the error prints.
Any idea where that error might be coming from (and why I am not seeing that error when running SwiftData from other package executables)?
Hi,
is there any description/documentation about what can not be used as SwiftData attributes?
I do not mean things which cause issues at compile time, like having a type which is not Codeable. But rather I am looking for info which things to avoid, if you do not want to run into application crashes in modelContext.save(). Like for example having an enum which as an optional associated value as an attribute type (crashes on save is the associated value is nil).
Anybody seen any documentation about that? Or tech notes?
Thanks in advance for any hints :-).
Cheers, Michael
I came across of something I'm struggling to comprehend. I've got an iOS app based on SwiftUI and SwiftData + CloudKit. I wrote it using Xcode 15 and the target was iOS 17. Everything works fine in this environment, but after upgrading my phone to iOS 18 beta 7 something very strange started to happen with SwiftData on a physical device and in the simulator.
Every time when data is updated, to be precise - when the relationship is modified, the change is reverted after 15 seconds!
I've got the following settings on and nothing can be seen it's going on there in the logs
-com.apple.CoreData.Logging.stderr 1
-com.apple.CoreData.CloudKitDebug 1
-com.apple.CoreData.SQLDebug 1
-com.apple.CoreData.ConcurrencyDebug 1
Here you are some simplified code extraction:
@Model
final public class Note: Identifiable, Hashable
{
public private(set) var uuid = UUID().uuidString
var notification: Notification?
...
}
@Model
final public class Notification: Identifiable, Hashable
{
var dateId: String = ""
@Relationship(deleteRule: .nullify, inverse: \Note.notification) var notes: [Note]?
init(_ dateId: String) {
self.dateId = dateId
}
}
@ModelActor
final public actor DataModelActor : DataModel
{
public func updateNotification(oldDate: Date, newDate: Date? = nil, persistentModelId: PersistentIdentifier) {
if let note = modelContext.model(for: persistentModelId) as? Note {
updateNotification(oldDate: oldDate, newDate: newDate, note: note)
}
try? self.modelContext.save()
}
private func updateNotification(oldDate: Date? = nil, newDate: Date? = nil, note: Note) {
if let oldDate = oldDate {
let notifications = fetchNotifications()
let oldDateId = NotificationDateFactory.getId(from: oldDate)
// removing the note from the collection related to oldDate
if let notification = notifications.first(where: { $0.dateId == oldDateId }) {
if let notificationNotes = notification.notes {
if let notificationNoteIndex = notification.notes!.firstIndex(of: note) {
notification.notes!.remove(at: notificationNoteIndex)
}
if notification.notes == nil || notification.notes!.isEmpty {
self.modelContext.delete(notification)
}
}
}
}
if let newDate = newDate, newDate > Calendar.current.startOfToday() {
// adding to a new collection related to newDate
let notifications = fetchNotifications()
let newDateId = NotificationDateFactory.getId(from: newDate)
if let notification = notifications.first(where: { $0.dateId == newDateId }) {
note.notification = notification
} else {
let notification = Notification(newDateId)
note.notification = notification
}
}
}
}
Spreading save method here and there does not help :(
I've used Core Data Lab software to look into database and I can clearly see data changes are reverted for relationship property.
Example:
In Notification database there is one element:
2024-08-26 (3)
with 3 notes attached. I modified one note to send notification on 2024-08-27. Changes in database reflects situation correctly showing:
2014-08-26 (2)
2024-08-27 (1)
BUT!!! After 15 seconds doing noting database looks like this:
2024-08-26 (3)
2024-08-27 (0)
All changes were reverted and all notes are still attached to the same date as they were at the first place.
Any thoughts?
Hey everyone,
I’m relatively new to SwiftUI and iOS development, having started earlier this year, and I’m working on a Notes app using SwiftData. I’ve run into some issues when dealing with nested views.
In my app, I’m using a SwiftData @Query in a parent view and then passing the model objects down the view tree. However, I’m frequently encountering errors such as “No container found” or similar. To work around this, I’ve tried having the child views perform their own SwiftData @Query, with the parent view passing a string identifier to the child views. This approach has helped somewhat, but I’m still not entirely sure how to properly manage UI updates.
Additionally, I’ve noticed that turning on iCloud syncing seems to have made the problem worse. The errors have become more frequent, and it’s unclear to me how to handle these situations effectively.
Could someone explain the best practices for handling SwiftData queries in nested SwiftUI views, especially with iCloud syncing enabled? Any guidance or feedback would be greatly appreciated.
Thank you!
For example:
SELECT *
FROM accounts
WHERE (platform, innerID) NOT IN (
('platform_value1', 'innerID_value1'),
('platform_value2', 'innerID_value2'),
...
);
this is hard to use Swift Predicate:
func _fetchAccountNotIn(_ scope: [Account]) throws -> [Account] {
let scope = scope.map{ ($0.platform, $0.innerID) }
return try fetch(.init(predicate: #Predicate<Account> { !scope.contains(($0.platform, $0.innerID)) }))
}
shows compiler error: Cannot convert value of type '(String, String)' to expected argument type '((String, String)) throws -> Bool'
Account definition:
@Model
public final class Account {
#Unique<Account>([\.platform, \.innerID])
#Index<Account>([\.platform, \.innerID])
@Attribute(.preserveValueOnDeletion)
public private(set) var platform : String
@Attribute(.preserveValueOnDeletion)
public private(set) var innerID : String
}
In my SwiftData @Model I have an attribute attachments: [String]? and I'm getting an error when trying to save it:
CoreData: fault: Could not materialize Objective-C class named "Array" from declared attribute value type "Array<String>" of attribute named attachments.
Getting this error periodically on iOS 18 Beta 7 on iPhone, but the same code works on iPadOS 18 b7 and on macOS Sonoma (14.6.1)
Anyone seen this?
I'm using SwiftData with an @Model and am also using an @ModelActor. I've fixed all concurrency issues and have migrated to Swift 6. I am getting a console error that I do not understand how to clear. I get this error in Swift 6 and Swift 5. I do not experience any issue with the app. It seems to be working well. But I want to try to get all issues taken care of. I am using the latest Xcode beta.
error: the replacement path doesn't exist:
"/var/folders/1q/6jw9d6mn0gx1znh1n19z2v9r0000gp/T/swift-generated-sources/@_swiftmacro_17MyAppName14MyModelC4type18_PersistedPr> opertyfMa.swift"