I have a set of cascading dependent models. I want to create a preview container that adds them all to a preview container. I have created a model container which loads a recipe of my choice in sequentialy so the instantiation works. I noticed that everything loads but only 1 RecipeStep will ever be added. No matter how I do it, only one is added (randomly).
What is the best practice for creating a swiftdata preview with dependent models? How can I put them in a struct / class for easy access? I tried making a class that initialized the whole swiftdata object however the preview crashes when using this.
Note: I know one solution is that I could make child to parent fields optional, but I don't want to do that since I need them required.
@Model
class CocktailRecipe {
@Attribute(.unique) var id: UUID
@Attribute(.unique) var name: String
var info: String?
var menu: CocktailMenu?
@Relationship(deleteRule: .cascade, inverse: \Ingredient.recipe)
var ingredients: [Ingredient]
@Relationship(deleteRule: .cascade, inverse: \RecipeSection.recipe)
var sections: [RecipeSection]
var created_at: Date
init(name: String, info: String? = nil, menu: CocktailMenu? = nil, sections: [RecipeSection] = [], ingredients: [Ingredient] = []) {
self.name = name
self.info = info
self.menu = menu
self.sections = sections
self.ingredients = ingredients
self.created_at = Date()
self.id = UUID()
}
}
@Model
class Ingredient {
@Attribute(.unique) var id: UUID
@Attribute(.unique) var name: String
var quantity: Float?
var units: IngredientUnitType
var type: IngredientType
var recipe: CocktailRecipe
var created_at: Date
init(name: String, quantity: Float? = nil, units: IngredientUnitType, type: IngredientType, recipe: CocktailRecipe) {
self.name = name
self.quantity = quantity
self.units = units
self.type = type
self.recipe = recipe
self.created_at = Date()
self.id = UUID()
}
}
@Model
class RecipeSection {
@Attribute(.unique) var id: UUID
@Attribute(.unique) var title: String?
@Attribute(.unique) var index: Int
@Relationship(deleteRule: .cascade, inverse: \RecipeStep.section)
var steps: [RecipeStep]
var recipe: CocktailRecipe
var created_at: Date
init(title: String? = nil, recipe: CocktailRecipe, steps: [RecipeStep] = []) {
self.title = title
self.recipe = recipe
self.index = recipe.sections.count + 1
self.steps = steps
self.created_at = Date()
self.id = UUID()
}
}
@Model
class RecipeStep {
@Attribute(.unique) var id: UUID
@Attribute(.unique) var instruction: String
@Attribute(.unique) var index: Int
var section: RecipeSection
var created_at: Date
init(instruction: String, section: RecipeSection) {
self.instruction = instruction
self.section = section
self.index = section.steps.count + 1
self.created_at = Date()
self.id = UUID()
}
}
let cocktailPreviewContainer: ModelContainer = {
do {
let container = try ModelContainer(
for: CocktailMenu.self, CocktailRecipe.self, Ingredient.self, RecipeSection.self, RecipeStep.self, /*Bottle.self, Bar.self, */
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
let modelContext = container.mainContext
let spicyMargarita = CocktailRecipe(name: "Spicy Margarita")
modelContext.insert(spicyMargarita)
// Initialize ingredients
let spicyMargaritaIngredients = [
Ingredient(name: "Tequila", quantity: 2, units: .ounces, type: .tequila, recipe: spicyMargarita),
Ingredient(name: "Lime Juice", quantity: 1, units: .ounces, type: .mixer, recipe: spicyMargarita),
Ingredient(name: "Agave Syrup", quantity: 0.5, units: .ounces, type: .mixer, recipe: spicyMargarita),
// Ingredient(name: "Jalapeno", quantity: 3, units: .slices, type: .garnish, recipe: spicyMargarita)
]
for ingredient in spicyMargaritaIngredients {
modelContext.insert(ingredient)
}
try? modelContext.save()
// Initialize sections and steps
let preparationSection = RecipeSection(title: "title here", recipe: spicyMargarita)
modelContext.insert(preparationSection)
try? modelContext.save()
let step1 = RecipeStep(instruction: "Muddle the jalapeno slices in the shaker.", section: preparationSection)
modelContext.insert(step1)
let step2 = RecipeStep(instruction: "Add tequila, lime juice, and agave syrup to shaker with ice.", section: preparationSection)
modelContext.insert(step2)
let step3 = RecipeStep(instruction: "Shake well.", section: preparationSection)
modelContext.insert(step3)
let step4 = RecipeStep(instruction: "Strain into a chilled glass.", section: preparationSection)
modelContext.insert(step4)
return container
} catch {
fatalError("Failed to create container")
}
}()
var spicyMargarita: CocktailRecipe
init() {
// Initialize spicyMargarita recipe
spicyMargarita = CocktailRecipe(name: "Spicy Margarita")
// Initialize ingredients
let tequila = Ingredient(name: "Tequila", quantity: 2, units: .ounces, type: .tequila, recipe: spicyMargarita)
let limeJuice = Ingredient(name: "Lime Juice", quantity: 1, units: .ounces, type: .mixer, recipe: spicyMargarita)
let agaveSyrup = Ingredient(name: "Agave Syrup", quantity: 0.5, units: .ounces, type: .mixer, recipe: spicyMargarita)
let jalapeno = Ingredient(name: "Jalapeno", quantity: 3, units: .slices, type: .garnish, recipe: spicyMargarita)
// Initialize sections and steps
let preparationSection = RecipeSection(recipe: spicyMargarita)
let preparationSteps = [
RecipeStep(instruction: "Muddle the jalapeno slices in the shaker.", section: preparationSection),
RecipeStep(instruction: "Add tequila, lime juice, and agave syrup to shaker with ice.", section: preparationSection),
RecipeStep(instruction: "Shake well.", section: preparationSection),
RecipeStep(instruction: "Strain into a chilled glass.", section: preparationSection)
]
// WHEN THE BELOW IS COMMENTED OUT, no errors, but have many missing objects.
spicyMargarita.ingredients.append(contentsOf: [tequila, limeJuice, agaveSyrup, jalapeno])
spicyMargarita.sections.append(preparationSection)
preparationSection.steps.append(contentsOf: preparationSteps)
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
Is it possible to deploy SwiftData to server? Or is it a good direction to consider improve SwiftData?
I recently discovered the new documentation for enabling CloudKit sync with SwiftData. A key step I was missing in my previous setup was this step: Initialize the CloudKit development schema.
When I run this though, I get Core Data errors in my log...
CoreData: Unsupported attribute type
I've noticed that it seems to be preventing adding fields for my model properties that are Codable structs. I've been able to add Codable structs to my SwiftData models synced with CloudKit before. But using Core Data to initialize the CloudKit schema like the documentation suggests just doesn't work. Is there some other way around this? I'm about to just give up on CloudKit sync altogether. I need this to work because I'm trying to add some new fields and populate the fields with data based on existing data during the migration, but this seems to be preventing migration completely. If I don't initialize the schema, I get different errors and the ModelContainer won't initialize at all.
Hello. I had an issue that I was able to resolve, but I'd like to see if anyone might know why it happened in the first place. Essentially, I have a SwiftData model called Vehicle. This model has an array of Mileage, another SwiftData model. Vehicle has an init that accepts a Vehicle as a parameter and then matches it with that one.
init(from vehicle: Vehicle) {
self.id = vehicle.id
self.timestamp = vehicle.timestamp
self.year = vehicle.year
self.make = vehicle.make
self.model = vehicle.model
self.trim = vehicle.trim
self.mileage = vehicle.mileage
self.nickname = vehicle.nickname
}
Previously, I had the line self.mileageHistory = vehicle.mileageHistory in this init. However, that line caused a duplicate Vehicle model to be created and inserted into the context. I could tell because I had a List that was displaying all of the created Vehicle models from a Query.
It all stems from a view that is being displayed in a sheet. This view accepts a vehicle parameter called copy.
.sheet(isPresented: $edit, content: { VehicleEditView(vehicle: vehicle, copy: Vehicle(from: vehicle)) })
In a way I can understand why it's happening because a new Vehicle model is being created. But I don't understand why it only happens when the mileageHistory variables are set equal to each other. I removed that line and it doesn't do it anymore. I had to workaround it by setting the mileageHistory elsewhere.
Does anyone know why this might be happening?
I have two main models, House and Expense. On the home page I show a list of the three most recent expenses using the following query:
var descriptor = FetchDescriptor<Expense>(predicate: #Predicate { $0.houseID == house.id }, sortBy: [SortDescriptor(\.date, order: .reverse)])
descriptor.fetchLimit = 3
_recentExpenses = Query(descriptor)
I have a NavigationLink that takes you to the full list of expenses:
NavigationLink {
ExpenseListView(house: house)
} label: {
Text("See all")
}
On this page I have a query similar to the previous one, without the fetch limit:
let descriptor = FetchDescriptor<Expense>(
predicate: #Predicate { $0.houseID == house.id}
)
_expenses = Query(descriptor)
The problem is that when I click on the navigation link to go to the expenses page, the app freezes.
I tried passing the expenses array to the second page instead of invoking another query, but this way the view doesn't update when I update the expenses.
Is there a way to solve this problem? Or a way to pass a Binding of the fetched results?
Hi,
I started a new project selecting SwiftData as db by default.
I created my models as default.
@Model
class Trip {
var id: UUID
public private(set) var Name: String
public private(set) var Members: [Member]
public private(set) var Buys: [Buy]
public func removeBuy(indexBuyToRemove: Int) {
self.Buys.remove(at: indexBuyToRemove)
}
}
@Model
class Member: Identifiable, Equatable {
var id: UUID
public private(set) var Name: String
}
@Model
class Buy: Identifiable, Equatable {
var id: UUID
public private(set) var Author: Member
public private(set) var Ammount: Double
public private(set) var Title: String
}
I initialize my trips as this in my ContentView
@Query public var tripsList: [Trip]
And then pass this list in every other view.
I also have a ModelContext as default, passed like this in the @Main
@main
struct MainApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Member.self, Buy.self, Trip.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(.dark)
}
.modelContainer(sharedModelContainer)
}
}
And defining in the other views like this
@Environment(\.modelContext) var modelContext
Now. For the problem of consistency.
Whenever I add a new Trip I do this or delete (as .onDelete in the List)
public func addTrip(Trip: Trip) {
modelContext.insert(Trip)
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(tripsList[index])
}
}
}
And everything works fine.
The problem starts when I try to delete a Buy element of the Trip.Buys: [Buy].
I do like this:
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
let buyToRemove = trip.Buys[index]
trip.removeBuy(indexBuyToRemove: index)
do {
try modelContext.save()
} catch {}
}
}
}
The delete is not consistent.
What am I missing? What am I doing wrong?
Thank you for your help
Is there a way to view the data saved when using swiftdata? Even after deleting all models, the storage space taken up by the app in Settings is too large.
I am persisting a tree data structure Node using SwiftData with CloudKit enabled. I understand how to manage my NonSingleton models in SwiftUI. However, when a user first launches my app a Node model instance needs to be created that is separate from any NonSingleton model. So I think a Singleton class is appropriate but I don't know how to persist it?
@Model
final class Node {
var parent: Node?
@Relationship(deleteRule: .cascade, inverse: \Node.parent)
var children = [Node]()
// ...
}
@Model
final class NonSingleton {
@Relationship(deleteRule: .cascade) var node: Node
// ...
}
@Model // ?
final class Singleton {
static var node = Node()
private init() {}
}
Using: @Model class Object: Observable, Transferable
TableRow(object)
.draggable(object)
.dropDestination(for: Object.self) { _ in return }
(with or without the "return")
is the only .dropDestination syntax that compiles for me. Specifying the single return argument as
(droppedItems:[Account])
is tolerated
Using "return true" or expecting two returned items does not compile. I suspect the single returned item is the dropped items array, seems fair as "location" is not needed when using a TableRow. This is different behavior than all the docs and examples I've seen.
My Object is Codable and Transferable. A row can be dragged and a destination row highlights on hover-with-payload, but on drop I get a spinning wheel even though I've not specified any action yet. The action closure is not entered, even a simple print() statement is not reached.
I'd appreciate any thoughts on what might be the problem here. Debugger tips welcome, too.
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
Hello everyone,
How do I need to handle the delete with the relationship deny? When I delete a model that still has a reference to another model, it deletes it from the UI. But when I re-run the simulator, it's back again. Does someone have a hint for me?
How is it possible to ensure the uniqueness of the entries? Because I saw that the Attribute unique can't be used with CloudKit.
I am developing "Tasty Recipes" app. I want to load all data related to this app such "Recipe Category", "Recipes List", "Recipes Inscredient", "Preparation Methods" and so on. How can I load and store all data using SwiftData. User is going to use these data. They will update few things like "Favourite Recipes", "Rating". How can I do this.
Hello,
I've encountered an issue where SwiftData remains in the /Users/(username)/Library/Containers/MyApp directory even after my MacOS app has been deleted. This behavior is not what I expected.
Has anyone else faced this issue?
Also, I'm wondering if there is a delay in the deletion process that might account for this. is there a known time lag before everything is cleared out?
Thanks.
I'm not sure how to pull off a viewer/editor with SwiftData + SwiftUI that supports Cancel
I want the user to be able to:
Tap + to bring up a sheet/form to create a new Item.
The navigation bar shows Cancel and Create.
Create is only enabled when the fields are mostly valid.
Tap Create to do a deeper validation and add.
Tap Cancel to not make any changes.
Tap an item to view it.
Tap Edit when viewing to edit.
The navigation bar shows Cancel and Done.
Done is only enabled when the fields are mostly valid.
Tap Done to do a deeper validation and save changes.
Tap Cancel to not make any changes.
I think is a fairly common pattern. It's used by Contacts, for instance.
I've got parts of this working. Creating is easy: I just insert it only after the user taps Create and it's validated.
But Edit/Cancel/Done seems harder. If I disable autosaving the changes to the model, the changes are applied to other parts of SwiftUI immediately. And I haven't figured out how to cancel the changes. Should I be operating on a copy of the object instead? How do I copy it in a way that doesn't impact SwiftData?
I'm happy to take any tips you might have, but if you want to look at the code and suggest something that way I put my test case up here: https://github.com/tewha/ItemEditor My hope is I can get this sample working, get the changes into my app, and keep the sample up for others to discover (though I feel this would also be a great concept for sample code).
My app application uses pure Swift, SwiftUI, & SwiftData. One view contains a quiz. After a user completes a quiz I would like to display the results in another separate view.
My specific problem is passing data to sibling views (from origin view to destination view). If anyone could help I would appreciate it.
I’m using
@Model macro above my class
.modelContainer for designing/ designating the container
@Envioronment variable to insert the data into the context of the origin view.
Add same data (quiz result) to destination view.
I have a model class with various status values that I would like to store in a single 64 bits integer. For example one bit for this boolean flag, 4 bits for that lookup value and so on.
I tried to create a predicate containing bitwise operators to query specific bits but that seems impossible, no way no compile it.
is there a way I am no aware of to accomplish this, some workaround or is that planned to be possible in future Swiftdata release?
that would be a shame having to store a few status values of 4 or less bits each in different 64bits values, what a waste!
Thanks
Tof
In a new document-based UIKit app, specifying in the Info.plist an import type identifier of public.log that conforms to public.content, public.data, public.item accomplishes this.
In a new document-based SwiftUI multi-platform app, doing the same crashed at the DocumentGroup initializer with the error
_SwiftData_SwiftUI/Documents.swift:91: Fatal error: The document type is public.log which does not conform to com.apple.package.
This initializer expects the document type to be a package.
I am creating an app for organising books using SwiftUI and SwiftData. Adapting the code from Building a document-based app using SwiftData, I created the following model container for use in SwiftUI views (or the simulator).
@MainActor
let previewData: ModelContainer = {
do {
let bookSchema = Schema([Book.self])
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(
for: bookSchema,
configurations: configuration)
let modelContext = container.mainContext
if try modelContext.fetch(FetchDescriptor<Book>()).isEmpty {
var books = [
Book(name: "A book", year: 2024),
Book(name: "On Film Making", author: "Alexandar Mackendrick", year: 2005, isbn: try! validateISBN(isbn: "9780571211258"))
]
for book in books {
modelContext.insert(book)
print("Inserted book")
}
}
return container
} catch {
fatalError("Failed to create container")
}
}()
This builds fine, however, when I run it in previews, it crashes about 50% of the time with the following error:
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x60000370ca80> , -[__NSDictionaryM UTF8String]: unrecognized selector sent to instance 0x60000024c760 with userInfo of (null)
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryM UTF8String]: unrecognized selector sent to instance 0x60000024c760'
When researching the problem, I only found this GitHub issue from 2018, which was never resolved (it was closed due to inactivity). The issue comments suggest that it is a Core Data issue caused by a "dangling pointer" and "memory and race conditions".
This is the definition of the Book model. Most of the values are optional because the data will be entered automatically from sources that may have incomplete data.
@Model
final class Book {
var name: String?
var author: String?
var year: Int?
var isbn: ISBN?
var shelf: Shelf?
var collections: [Collection]
var marcXML: Data?
let dateCaptured: Date()
init(name: String? = nil, author: String? = nil, year: Int? = nil, isbn: ISBN? = nil, shelf: Shelf? = nil, marcXML: Data? = nil, collections: [Collection]? = nil) {
self.name = name
self.author = author
self.year = year
self.isbn = isbn
self.shelf = shelf
self.dateCaptured = Date()
self.marcXML = marcXML
self.collections = collections ?? []
}
}
Hello everyone,
I followed this (https://www.hackingwithswift.com/quick-start/swiftdata/how-to-sync-swiftdata-with-icloud) guide from Paul Hudson on how to sync swiftdata with iCloud. I tried it on my device directly and it worked exactly as I would expect. Now I tried the same version of the app on another device installed through TestFlight external tester group. It no longer works.
When deleting the app, the alert reads "Deleting this app will also delete its data, but any documents or data will be stored in iCloud will not be deleted" so the app should have said something in iCloud. When looking in Settings -> Your Name -> iCloud -> Manage Account Storage, on the working device I can see around 300 KB saved in iCloud, on the other device my app is not listed. Both have a fast and working internet connection, almost fully charged, low data mode turned off, running 17.4.1, Background modes enabled, Mobile data enabled, more than enough unused iCloud storage and plenty of time to sync.
I was streaming the logs from the Cloudkit dashboard but nothing appeared there. The data saved neither syncs to another device of the same apple id nor reappears after app removal.
The model Container is initialized without any configuration, the only relationship is optional and every value has a default value. There is A LOT of log noise when launching the app and I am unable to get any meaningful information from that. I can only get the log from the device where it is working as expected.
I have triple checked that it is exactly the same app version and no debug conditions anywhere. I have absolutely no idea what is causing this.
Thanks for any help :)
Hi there, i'm currently trying to make a ChatView with SwiftUI. I've been using "inverted" ScrollView, but now I see new defaultScrollAnchor and scrollPosition methods which can make solution more kind of native and less buggy.
So, here is the snippet of my implementation:
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageView(
message: message,
actions: viewModel.messageActions
)
}
}
}
.defaultScrollAnchor(.bottom)
At this point it works as expected most of the time. But once I have a message which content doesn't fit on the screen there is the problem.
When I enter the chat for the first time all is OK:
But next times I open the chat i see only this:
But the content is scrollable, once i scroll a little bit to the top, messages appear like they just were scrolled.
I think this is the problem with LazyVStack, maybe it waits for the content top to be displayed to render item?
Here is the full code of my ChatView:
import Foundation
import SwiftUI
import SwiftData
import AlertKit
import Factory
struct ChatView: View {
@StateObject private var viewModel: ChatViewModel
@Environment(\.modelContext) private var modelContext
@Query private var messages: [ChatMessage]
@Query private var generatingMessages: [ChatMessage]
init(chat: Chat) {
_viewModel = StateObject(wrappedValue: Container.shared.chatViewModel(chat))
let chatId = chat.persistentModelID
_messages = Query(
filter: #Predicate {
$0.chat?.persistentModelID == chatId
},
sort: \.date,
animation: .spring
)
_generatingMessages = Query(
filter: #Predicate {
$0.generating && $0.chat?.persistentModelID == chatId
}
)
}
var body: some View {
VStack(spacing: 0) {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(messages) { message in
MessageView(
message: message,
actions: viewModel.messageActions
)
}
}
}
.defaultScrollAnchor(.bottom)
if !messages.isEmpty { Divider() }
HStack(alignment: .bottom) {
TextField("message", text: $viewModel.text, axis: .vertical)
.padding(.vertical, 7)
.padding(.horizontal, 12)
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 20))
.onSubmit {
sendMessage()
}
SendButton(enabled: generatingMessages.isEmpty) {
sendMessage()
}
.animation(.default, value: generatingMessages.count)
.padding(.vertical, 7)
}
.padding(.horizontal)
.padding(.vertical, 10)
}
.toastingErrors(with: viewModel)
.scrollDismissesKeyboard(.interactively)
.navigationTitle(viewModel.chat.title ?? "")
.navigationBarTitleDisplayMode(.inline)
}
private func sendMessage() {
if viewModel.text.isEmpty {
return
}
viewModel.startLoading()
}
}
So, is there any workaround for this issue? Or maybe I do something wrong?
Hello together,
I hope someone can answer and can give me maybe a little guidance.
Is it possible to access and change a child value from a child class in SwiftData?
I tried now different ways but couldn't get it to work although I don't get any errors but I am not able to change or set any values.
So the code example below is only a simplified example and I know some minor errors but it is about the principle. Can the Father class access somehow the Children class and for example read the childrenName?
@Model class Father {
var father: String
var child: Child?
}
@Model class Child {
var childName: String
var child: Children?
var father: Father
}
@Model class Children {
var childrenName: String
var parent: Child
}
I don't need direct access from the Father to the Children class - it has to go via the child class.
Thanks for any direction or feedback in advance.