Post not yet marked as solved
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
Post not yet marked as solved
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?
Post not yet marked as solved
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?
Post not yet marked as solved
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?
I did manage to save my Entities to CloudKit with SwiftData but the default database is the private database. I need to store some Entities in the private and other Entities in the public CloudKit database. How do I manage that with SwiftData? With CoreData I always used different configurations for both private and public and added the entities to one or the other.
Post not yet marked as solved
Hi,
in the session the following is mentioned:
If a trip already exists with that name, then the persistent back end will update to the latest values. This is called an upsert. An upsert starts as an insert. If the insert collides with existing data, it becomes an update and updates the properties of the existing data.
Nevertheless, if I have a unique constraint on an (String) attribute and try to insert the same again, I end up in the debugger in the generated getter of the attribute:
@Attribute(.unique) public var name: String
{
get {
_$observationRegistrar.access(self, keyPath: \.name)
return self.getValue(for: \.name) // <- here
}
EXC_BREAKPOINT (code=1, subcode=0x1a8d6b724)
Am I missing something? If this is expected behaviour, how should I prevent this crash (other than checking for uniqueness before every insert)?
Thank you!
Cheers, Michael
Post not yet marked as solved
The 'unique' attribute is a really nice feature, BUT. In some of my apps, the unique identifier for an object is a combination of multiple attributes. (Example: a book title is not unique, but a combination of book title and author list is.)
How do I model this with SwiftData? I cannot use @Attribute(.unique) on either the title OR the author list, but I want SwiftData to provide the same "insert or update" logic.
Is this possible?
Post not yet marked as solved
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)
Post not yet marked as solved
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)
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)
}
}
Post not yet marked as solved
If I use a Codable enum as the type in an array property on a Model, I get a crash. The message is something like:
Illegal attempt to use a Optional(Swift.Mirror.DisplayStyle.enum) as a Property
Minor change code example from a fresh project using SwiftData:
@Model
final class Item {
var timestamp: Date
var choices: [Choice]
init(timestamp: Date,
choices: [Choice]) {
self.timestamp = timestamp
self.choices = choices
}
}
enum Choice: String, Codable {
case beep, bloop
}
ContentView (largely unchanged):
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
VStack {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
Text("\(item.choices.map({"\($0.rawValue)"}).joined())")
}
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date(), choices: [.bloop, .beep])
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
This is strange and seems like a bug as if I use Codable structs in this instance it is fine. Yet, if that Codable struct has an enum as a property, it also crashes with a different error.
Working:
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
var choices: [Choice]
init(timestamp: Date,
choices: [Choice]) {
self.timestamp = timestamp
self.choices = choices
}
}
struct Choice: Codable {
let foo: String
}
Also doesn't work:
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
var choices: [Choice]
init(timestamp: Date,
choices: [Choice]) {
self.timestamp = timestamp
self.choices = choices
}
}
struct Choice: Codable {
enum SpecificChoice: String, Codable {
case beep, bloop
}
let foo: SpecificChoice
}
It seems really odd and arbitrary that an enum would cause these issues. There may be other cases involving enums, but to wrap up it seems enums as properties of Codables that live within an array on a SwiftData model, or an array of enums on a SwiftData model cause crashes.
Post not yet marked as solved
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")
}
}
}
}
Post not yet marked as solved
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.
Post not yet marked as solved
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?
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
Post not yet marked as solved
Hi,
if I have a @Model class there's always an id: PersistentIdentifier.ID underneath which, according to the current documentation "The value that uniquely identifies the associated model within the containing store.".
So I am wondering if it is (good) enough to rely on this attribute to uniquely identify @Model class entities, or if there are edge cases where it does not work (like maybe when using CloudKit)?
If anybody saw some information regarding this, please let me know :-)
Cheers,
Michael
Post not yet marked as solved
Hi,
when inserting an entity with a relationship I get the following runtime error:
Illegal attempt to establish a relationship 'group' between objects in different contexts [...].
The model looks like this:
@Model
class Person {
var name: String
@Relationship(.nullify, inverse: \Group.members) var group: Group
init(name: String) {
self.name = name
}
}
@Model
class Group {
var name: String
@Relationship(.cascade) public var members: [Person]
init(name: String) {
self.name = name
}
}
It can be reproduced using this (contrived) bit of code:
let group = Group(name: "Group A")
ctx.insert(group)
try! ctx.save()
let descriptor = FetchDescriptor<Group>()
let groups = try ctx.fetch(descriptor)
XCTAssertFalse(groups.isEmpty)
XCTAssertEqual(groups.count, 1)
XCTAssertTrue(groups.first?.name == "Group A")
let person = Person(name: "Willy")
person.group = group
ctx.insert(person)
try ctx.save()
(See also full test case below).
Anybody experiencing similar issues? Bug or feature?
Cheers, Michael
Full test case:
import SwiftData
import SwiftUI
import XCTest
// MARK: - Person -
@Model
class Person {
var name: String
@Relationship(.nullify, inverse: \Group.members) var group: Group
init(name: String) {
self.name = name
}
}
// MARK: - Group -
@Model
class Group {
var name: String
@Relationship(.cascade) public var members: [Person]
init(name: String) {
self.name = name
}
}
// MARK: - SD_PrototypingTests -
final class SD_PrototypingTests: XCTestCase {
var container: ModelContainer!
var ctx: ModelContext!
override func setUpWithError() throws {
let fullSchema = Schema([Person.self,
Group.self,])
let dbCfg = ModelConfiguration(schema: fullSchema)
container = try ModelContainer(for: fullSchema, dbCfg)
ctx = ModelContext(container)
_ = try ctx.delete(model: Group.self)
_ = try ctx.delete(model: Person.self)
}
override func tearDownWithError() throws {
guard let dbURL = container.configurations.first?.url else {
XCTFail("Could not find db URL")
return
}
do {
try FileManager.default.removeItem(at: dbURL)
} catch {
XCTFail("Could not delete db: \(error)")
}
}
func testRelAssignemnt_FB12363892() throws {
let group = Group(name: "Group A")
ctx.insert(group)
try! ctx.save()
let descriptor = FetchDescriptor<Group>()
let groups = try ctx.fetch(descriptor)
XCTAssertFalse(groups.isEmpty)
XCTAssertEqual(groups.count, 1)
XCTAssertTrue(groups.first?.name == "Group A")
let person = Person(name: "Willy")
person.group = group
ctx.insert(person)
try ctx.save()
}
}
Post not yet marked as solved
I’m working on an app where I have some sensitive data that I want to encrypt when persisting. It looks like SwiftData has a BackingData protocol but it mentions it's for in-memory only.
Is there a way to provide a custom storage for SwiftData so that I can encrypt the data before saving?
Post not yet marked as solved
Does SwiftData support UIImage as in CoreData specified here: https://www.swiftdevjournal.com/saving-images-in-core-data/ If it does, how to specify that in the @Model schema, especially using external storage to save the image in a separate file. Thanks.
Post not yet marked as solved
I'm testing the new SwiftData to see if I can use it on one of my apps. Specifically I'm trying to learn how to use the schema migrations.
I made a new project using Xcode 15 beta 4. I selected to use SwiftData and iCloud.
I ran the generated project and I added a couple of items to the screen.
Then, I wanted to make a schema migration, so I changed the Item model from:
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
to also include a string title:
@Model
final class Item {
var timestamp: Date
var title: String
init(timestamp: Date, title: String) {
self.timestamp = timestamp
self.title = title
}
}
I also made some changes to the SwiftUI code to include an empty string when adding a new item, so it could compile:
let newItem = Item(timestamp: Date(), title: "")
And then I added a new file containing the migration, according to what I saw on the video sessions:
//
// SwiftDataMigrations.swift
// Test 1
//
// Created by Diego on 12/07/23.
//
import Foundation
import SwiftData
enum ItemSchemaV1: VersionedSchema {
static var versionIdentifier: String?
static var models: [any PersistentModel.Type] {
[Item.self]
}
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
}
enum ItemSchemaV2: VersionedSchema {
static var versionIdentifier: String?
static var models: [any PersistentModel.Type] {
[Item.self]
}
@Model
final class Item {
var timestamp: Date
var title: String
init(timestamp: Date, title: String) {
self.timestamp = timestamp
self.title = title
}
}
}
enum ItemsMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[ItemSchemaV1.self, ItemSchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: ItemSchemaV1.self, toVersion: ItemSchemaV2.self)
}
After that, I specified the migrationPlan in the app:
//
// Test_1App.swift
// Test 1
//
// Created by Diego on 12/07/23.
//
import SwiftUI
import SwiftData
@main
struct Test_1App: App {
var container = try! ModelContainer(
for: Item.self,
migrationPlan: ItemsMigrationPlan.self
)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
And then I tried to run the app but it crashed on launch with this error:
SwiftData/ModelContext.swift:179: Fatal error: Container does not have any data stores
Does anyone have any idea about what I might be missing?
The only thing that didn't matched the videos was that when creating a ModelContainer, the code on the video showed var container = ModelContainer... but the compiler showed the error Call can throw, but errors cannot be thrown out of a property initializer, so I added the try!. Also, on the VersionedSchema, I had to add static var versionIdentifier: String?.
Other than that I have no idea.
Thanks in advance.