Model your schema with SwiftData

RSS for tag

Discuss the WWDC23 Session Model your schema with SwiftData

Posts under wwdc2023-10195 tag

31 Posts

Post

Replies

Boosts

Views

Activity

SwiftData Query relationships not working
Overview I have 2 models: Deparment and Student Each Department can contain multiple students Each Student can only be in one Department I have DepartmentList, tapping on the department should take it to the StudentList which lists all students in the department Problem When I use Query in StudentList to filter only students for a specific department id, no students are shown. Questions: What should I do to list the students in a department? (see complete code below). let filter = #Predicate<Student> { student in student.department?.id == departmentID } let query = Query(filter: filter, sort: \.name) _students = query Complete code App @main struct SchoolApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [Department.self, Student.self]) } } Department import Foundation import SwiftData @Model class Department { var id: UUID var name: String var students: [Student] init( id: UUID, name: String, students: [Student] = [] ) { self.id = id self.name = name self.students = students } } Student import Foundation import SwiftData @Model class Student { var id: UUID var name: String @Relationship(inverse: \Department.students) var department: Department? init( id: UUID, name: String, department: Department? = nil ) { self.id = id self.name = name self.department = department } } ContentView import SwiftUI struct ContentView: View { @State private var selectedDepartment: Department? var body: some View { NavigationSplitView { DepartmentList(selectedDepartment: $selectedDepartment) } detail: { if let department = selectedDepartment { StudentList(department: department) } else { Text("no department selected") } } .task { printStoreFilePath() } } private func printStoreFilePath() { let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) if let path = urls.map({ $0.path(percentEncoded: false) }).first { print("Storage: \(path)") } } } DepartmentList import SwiftUI import SwiftData struct DepartmentList: View { @Binding var selectedDepartment: Department? @Query(sort: \.name) private var departments: [Department] @Environment(\.modelContext) private var modelContext var body: some View { List(selection: $selectedDepartment) { ForEach(departments) { department in NavigationLink(value: department) { Text(department.name) } } } .toolbar { ToolbarItem { Button { addDepartment() } label: { Label("Add", systemImage: "plus") } } } } private func addDepartment() { guard let index = (1000..<10000).randomElement() else { return } let department = Department(id: UUID(), name: "Department \(index)") modelContext.insert(department) } } StudentList import SwiftUI import SwiftData struct StudentList: View { var department: Department @Query private var students: [Student] @Environment(\.modelContext) private var modelContext init(department: Department) { self.department = department let departmentID = department.id let filter = #Predicate<Student> { student in student.department?.id == departmentID } let query = Query(filter: filter, sort: \.name) _students = query } var body: some View { List { ForEach(students) { student in Text(student.name) } } .toolbar { ToolbarItem { Button { addStudent() } label: { Label("Add", systemImage: "plus") } } } } private func addStudent() { guard let index = (1000..<10000).randomElement() else { return } let student = Student( id: UUID(), name: "Student \(index)", department: department ) modelContext.insert(student) } }
10
4
4.1k
Jul ’23
Can't test equality of two model entities with a predicate
Hi, given this model: @Model class OnlyName { var name: String init(name: String) { self.name = name } } I would assume that I could write a predicate like this: #Predicate<OnlyName> { $0.name == other.name }, where other is also an instance of OnlyName for example returned by an earlier fetch. Unfortunately this results in the following compiler errors: Initializer 'init(_:)' requires that 'OnlyName' conform to 'Encodable' Initializer 'init(_:)' requires that 'OnlyName' conform to 'Decodable' Any idea if this is a bug in SwiftData or if I am missing something? Cheers, Michael
2
0
1.5k
Jun ’23
Modelling dependencies
I'm stuck at an error EXC_BREAKPOINT (code=1, subcode=0x1a8d69a38)that is thrown in a class during initialization. The class is defined as: @Model public final class Parent { @Attribute(.unique) public var uuid: UUID /// The date specification. @Relationship(.cascade) public var dateSpec: DateSpec /// The title. public var title: Title /// The subtitle. public var subTitle: Subtitle public init(uuid: UUID, dateSpec: DateSpec, title: Title, subTitle: Subtitle) { self.uuid = uuid self.dateSpec = dateSpec self.title = title self.subTitle = subTitle } } The error is thrown in the var dateSpec property at the return self.getValue(for: \.dateSpec) call of the @PersistedProperty macro. DateSpec is defined this way: @Model public final class DateSpec { @Attribute(.unique) public var uuid: UUID /// The type of the date specification (`.point` or `.range`). public var type: DateSpecType @Relationship(.cascade) public var point: DatePoint @Relationship(.cascade) public var range: DateRange public init(uuid: UUID, type: DateSpecType = .none, point: DatePoint = .init(), range: DateRange = .init()) { self.uuid = uuid self.type = type self.point = point self.range = range } } And DatePoint is defined so: @Model public final class DatePoint { @Attribute(.unique) public var uuid: UUID public var format: String public var date: Date? public init(uuid: UUID, format: String, date: Date? = nil) { self.uuid = uuid self.format = format self.date = date } } (DateRange accordingly). So, as far as I understood the sessions, this should work. Or did I miss something? -- Edit: When taking out DatePoint and DateRange from the model, and replacing the properties by .transient wrappers that get/set the respective properties directly in DateSpec, then the error disappears. So, is the problem the cascading of the relationships between Parent and DateSpec, and DateSpec and DatePoint/DateRange?
2
0
1.1k
Jun ’23
Relationships are not persisted unless there is an inverse?
Hi, I encountered the issue, that unless an inverse relationship is modelled, the relationship is not persisted. This can be reproduced with the sample code below: Press the "Add Person" button twice Then press the "Add group" button You now can see that the group has to member, but once you restart the app the members a gone. Once an inverse relationship is added (see commented code) the relationships are persisted. Any idea if this is intended behaviour? import SwiftData import SwiftUI // MARK: - Person - @Model class Person { var name: String // uncomment to make it work @Relationship(.nullify) var group: Group? init(name: String) { self.name = name } } // MARK: - Group - @Model class Group { var name: String // uncomment to make it work @Relationship(.nullify, inverse: \Person.group) public var members: [Person] @Relationship(.nullify) public var members: [Person] // comment to make it work init(name: String) { self.name = name } } // MARK: - SD_PrototypingApp - @main struct SD_PrototypingApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [Person.self, Group.self]) } } // MARK: - ContentView - struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var groups: [Group] @Query private var persons: [Person] var body: some View { VStack { ForEach(groups) { group in Text("\(group.name): \(group.members.count)") } ForEach(persons) { person in Text("Person: \(person.name)") } Button { assert(persons.isEmpty == false) if groups.isEmpty { let group = Group(name: "Group A") group.members = persons modelContext.insert(group) try! modelContext.save() } } label: { Text("Add a group") } .disabled(!groups.isEmpty || persons.isEmpty) Button { let person = Person(name: "Person \(Int.random(in: 0 ... 1_000_000))") modelContext.insert(person) } label: { Text("Add Person") } } } }
0
1
1.5k
Jun ’23
[SwiftData]Why did not I receive willMigrate and didMigrate callbacks during the schemaMigrationPlan
I used Schema and MigrationPlan to initialize the Model Container. SwiftDataDemoApp.swift Then I modified the Schema and created a MigrationStage through the custom method, which was configured in the stages array of the SchemaMigrationPlan. Models.swift But when I run the app, I don't receive the willMigrate and didMigrate callbacks Xcode Version: 15.0 beta (15A5160n) MacOS: 13.3.1 (a) (22E772610a)
2
0
1.1k
Jun ’23
How to create a ModelContainer with [PersistentModel] and SchemaMigrationPlan
I can't find the method to create ModelContainer that appears in the video at 8: 52. The code in the video is as follows: let container = ModelContainer( for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self ) This method doesn't seem to exist in Xcode15. I found other method to create ModelContainer using Schema and Schema MigrationPlan in Xcode15. public convenience init(for givenSchema: Schema, migrationPlan: (SchemaMigrationPlan.Type)? = nil, _ configurations: ModelConfiguration...) throws And I tried to create a Schema to use this method, like this: let container = try! ModelContainer(for: .init([Person.self]), migrationPlan: MigrationPlan.self) But an error occurred during runtime SwiftData/ModelContext.swift:177: Fatal error: Container does not have any data stores SwiftDataDemoApp.swift How to create a ModelContainer with [PersistentModel] and SchemaMigrationPlan ? Xcode Version: 15.0 beta (15A5160n) MacOS: 13.3.1 (a) (22E772610a)
1
2
1.4k
Jun ’23
What about CloudKit?
I am watching Swiftdata videos, but so far they haven't mentioned CloudKit explicitly, and I'm not sure what is the relationship between SwiftData and Cloudkit at this time. Are these models automatically synced with Cloudkit? If that's the case, do the migration plans work for cloudkit as well?
1
1
1k
Jun ’23
@Attribute(originalName:)
Hello, the way I understand the schema migrations in conjunction with the originalName modifier, is that I will always need this property rapper for future migrations of my app. Is that correct? @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date Let's say I do a V4 migration and do another change (irrelevant in this context; can be anything). Do startDate and endDate each need @Attribute(originalName: <#String>) or has SwiftData already renamed those attributes in the store and therefore I wouldn't need this anymore? Thank you! Denis
0
0
522
Jun ’23
SwiftData Query relationships not working
Overview I have 2 models: Deparment and Student Each Department can contain multiple students Each Student can only be in one Department I have DepartmentList, tapping on the department should take it to the StudentList which lists all students in the department Problem When I use Query in StudentList to filter only students for a specific department id, no students are shown. Questions: What should I do to list the students in a department? (see complete code below). let filter = #Predicate<Student> { student in student.department?.id == departmentID } let query = Query(filter: filter, sort: \.name) _students = query Complete code App @main struct SchoolApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [Department.self, Student.self]) } } Department import Foundation import SwiftData @Model class Department { var id: UUID var name: String var students: [Student] init( id: UUID, name: String, students: [Student] = [] ) { self.id = id self.name = name self.students = students } } Student import Foundation import SwiftData @Model class Student { var id: UUID var name: String @Relationship(inverse: \Department.students) var department: Department? init( id: UUID, name: String, department: Department? = nil ) { self.id = id self.name = name self.department = department } } ContentView import SwiftUI struct ContentView: View { @State private var selectedDepartment: Department? var body: some View { NavigationSplitView { DepartmentList(selectedDepartment: $selectedDepartment) } detail: { if let department = selectedDepartment { StudentList(department: department) } else { Text("no department selected") } } .task { printStoreFilePath() } } private func printStoreFilePath() { let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) if let path = urls.map({ $0.path(percentEncoded: false) }).first { print("Storage: \(path)") } } } DepartmentList import SwiftUI import SwiftData struct DepartmentList: View { @Binding var selectedDepartment: Department? @Query(sort: \.name) private var departments: [Department] @Environment(\.modelContext) private var modelContext var body: some View { List(selection: $selectedDepartment) { ForEach(departments) { department in NavigationLink(value: department) { Text(department.name) } } } .toolbar { ToolbarItem { Button { addDepartment() } label: { Label("Add", systemImage: "plus") } } } } private func addDepartment() { guard let index = (1000..<10000).randomElement() else { return } let department = Department(id: UUID(), name: "Department \(index)") modelContext.insert(department) } } StudentList import SwiftUI import SwiftData struct StudentList: View { var department: Department @Query private var students: [Student] @Environment(\.modelContext) private var modelContext init(department: Department) { self.department = department let departmentID = department.id let filter = #Predicate<Student> { student in student.department?.id == departmentID } let query = Query(filter: filter, sort: \.name) _students = query } var body: some View { List { ForEach(students) { student in Text(student.name) } } .toolbar { ToolbarItem { Button { addStudent() } label: { Label("Add", systemImage: "plus") } } } } private func addStudent() { guard let index = (1000..<10000).randomElement() else { return } let student = Student( id: UUID(), name: "Student \(index)", department: department ) modelContext.insert(student) } }
Replies
10
Boosts
4
Views
4.1k
Activity
Jul ’23
Is SwiftData Secure?
How secure storing the data using SwiftData? What encryption mechanism is it used to store data? Our project has a requirement to shore data using AES 256 encryption.
Replies
2
Boosts
0
Views
2.2k
Activity
Jun ’23
Swift Data for one off data storage
What is the best way to use swift data to store one off data models? For example an application state that you want to be persisted across application launches. The only examples I've seen use arrays of items which wouldn't work for having just one application state. Is it possible to query just one item?
Replies
3
Boosts
0
Views
2.2k
Activity
Jun ’23
Can't test equality of two model entities with a predicate
Hi, given this model: @Model class OnlyName { var name: String init(name: String) { self.name = name } } I would assume that I could write a predicate like this: #Predicate<OnlyName> { $0.name == other.name }, where other is also an instance of OnlyName for example returned by an earlier fetch. Unfortunately this results in the following compiler errors: Initializer 'init(_:)' requires that 'OnlyName' conform to 'Encodable' Initializer 'init(_:)' requires that 'OnlyName' conform to 'Decodable' Any idea if this is a bug in SwiftData or if I am missing something? Cheers, Michael
Replies
2
Boosts
0
Views
1.5k
Activity
Jun ’23
Modelling dependencies
I'm stuck at an error EXC_BREAKPOINT (code=1, subcode=0x1a8d69a38)that is thrown in a class during initialization. The class is defined as: @Model public final class Parent { @Attribute(.unique) public var uuid: UUID /// The date specification. @Relationship(.cascade) public var dateSpec: DateSpec /// The title. public var title: Title /// The subtitle. public var subTitle: Subtitle public init(uuid: UUID, dateSpec: DateSpec, title: Title, subTitle: Subtitle) { self.uuid = uuid self.dateSpec = dateSpec self.title = title self.subTitle = subTitle } } The error is thrown in the var dateSpec property at the return self.getValue(for: \.dateSpec) call of the @PersistedProperty macro. DateSpec is defined this way: @Model public final class DateSpec { @Attribute(.unique) public var uuid: UUID /// The type of the date specification (`.point` or `.range`). public var type: DateSpecType @Relationship(.cascade) public var point: DatePoint @Relationship(.cascade) public var range: DateRange public init(uuid: UUID, type: DateSpecType = .none, point: DatePoint = .init(), range: DateRange = .init()) { self.uuid = uuid self.type = type self.point = point self.range = range } } And DatePoint is defined so: @Model public final class DatePoint { @Attribute(.unique) public var uuid: UUID public var format: String public var date: Date? public init(uuid: UUID, format: String, date: Date? = nil) { self.uuid = uuid self.format = format self.date = date } } (DateRange accordingly). So, as far as I understood the sessions, this should work. Or did I miss something? -- Edit: When taking out DatePoint and DateRange from the model, and replacing the properties by .transient wrappers that get/set the respective properties directly in DateSpec, then the error disappears. So, is the problem the cascading of the relationships between Parent and DateSpec, and DateSpec and DatePoint/DateRange?
Replies
2
Boosts
0
Views
1.1k
Activity
Jun ’23
Relationships are not persisted unless there is an inverse?
Hi, I encountered the issue, that unless an inverse relationship is modelled, the relationship is not persisted. This can be reproduced with the sample code below: Press the "Add Person" button twice Then press the "Add group" button You now can see that the group has to member, but once you restart the app the members a gone. Once an inverse relationship is added (see commented code) the relationships are persisted. Any idea if this is intended behaviour? import SwiftData import SwiftUI // MARK: - Person - @Model class Person { var name: String // uncomment to make it work @Relationship(.nullify) var group: Group? init(name: String) { self.name = name } } // MARK: - Group - @Model class Group { var name: String // uncomment to make it work @Relationship(.nullify, inverse: \Person.group) public var members: [Person] @Relationship(.nullify) public var members: [Person] // comment to make it work init(name: String) { self.name = name } } // MARK: - SD_PrototypingApp - @main struct SD_PrototypingApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [Person.self, Group.self]) } } // MARK: - ContentView - struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var groups: [Group] @Query private var persons: [Person] var body: some View { VStack { ForEach(groups) { group in Text("\(group.name): \(group.members.count)") } ForEach(persons) { person in Text("Person: \(person.name)") } Button { assert(persons.isEmpty == false) if groups.isEmpty { let group = Group(name: "Group A") group.members = persons modelContext.insert(group) try! modelContext.save() } } label: { Text("Add a group") } .disabled(!groups.isEmpty || persons.isEmpty) Button { let person = Person(name: "Person \(Int.random(in: 0 ... 1_000_000))") modelContext.insert(person) } label: { Text("Add Person") } } } }
Replies
0
Boosts
1
Views
1.5k
Activity
Jun ’23
[SwiftData]Why did not I receive willMigrate and didMigrate callbacks during the schemaMigrationPlan
I used Schema and MigrationPlan to initialize the Model Container. SwiftDataDemoApp.swift Then I modified the Schema and created a MigrationStage through the custom method, which was configured in the stages array of the SchemaMigrationPlan. Models.swift But when I run the app, I don't receive the willMigrate and didMigrate callbacks Xcode Version: 15.0 beta (15A5160n) MacOS: 13.3.1 (a) (22E772610a)
Replies
2
Boosts
0
Views
1.1k
Activity
Jun ’23
How to create a ModelContainer with [PersistentModel] and SchemaMigrationPlan
I can't find the method to create ModelContainer that appears in the video at 8: 52. The code in the video is as follows: let container = ModelContainer( for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self ) This method doesn't seem to exist in Xcode15. I found other method to create ModelContainer using Schema and Schema MigrationPlan in Xcode15. public convenience init(for givenSchema: Schema, migrationPlan: (SchemaMigrationPlan.Type)? = nil, _ configurations: ModelConfiguration...) throws And I tried to create a Schema to use this method, like this: let container = try! ModelContainer(for: .init([Person.self]), migrationPlan: MigrationPlan.self) But an error occurred during runtime SwiftData/ModelContext.swift:177: Fatal error: Container does not have any data stores SwiftDataDemoApp.swift How to create a ModelContainer with [PersistentModel] and SchemaMigrationPlan ? Xcode Version: 15.0 beta (15A5160n) MacOS: 13.3.1 (a) (22E772610a)
Replies
1
Boosts
2
Views
1.4k
Activity
Jun ’23
What about CloudKit?
I am watching Swiftdata videos, but so far they haven't mentioned CloudKit explicitly, and I'm not sure what is the relationship between SwiftData and Cloudkit at this time. Are these models automatically synced with Cloudkit? If that's the case, do the migration plans work for cloudkit as well?
Replies
1
Boosts
1
Views
1k
Activity
Jun ’23
Schema Plan Current Version
I understand that in the demo we have version 1 to 3 in the schema plan, but where does the current version live, the one with relationship decorations? And how is the current version's migration type declared or handled?
Replies
0
Boosts
0
Views
626
Activity
Jun ’23
@Attribute(originalName:)
Hello, the way I understand the schema migrations in conjunction with the originalName modifier, is that I will always need this property rapper for future migrations of my app. Is that correct? @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date Let's say I do a V4 migration and do another change (irrelevant in this context; can be anything). Do startDate and endDate each need @Attribute(originalName: <#String>) or has SwiftData already renamed those attributes in the store and therefore I wouldn't need this anymore? Thank you! Denis
Replies
0
Boosts
0
Views
522
Activity
Jun ’23