Post not yet marked as solved
.modelContainer(for: MyMode.self, isUndoEnabled: true)
This may work for single model containers, but I have a number of models. I don't see how to enable undo for multiple model containers.
Post marked as Apple Recommended
Hi,
has anybody managed to get two sqlite stores working? If I define the stores with a configuration for each it seems like that only the first configuration and and therefore the store is recognised.
This is how I define the configuration and container:
import SwiftData
@main
struct SwiftDataTestApp: App {
var modelContainer: ModelContainer
init() {
let fullSchema = Schema([
SetModel.self,
NewsModel.self
])
let setConfiguration = ModelConfiguration(
"setconfig",
schema: Schema([SetModel.self]),
url: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Sets.sqlite"),
readOnly: false)
let newsConfiguration = ModelConfiguration(
"newsconfig",
schema: Schema([NewsModel.self]),
url: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("News.sqlite"),
readOnly: false)
modelContainer = try! ModelContainer(for: fullSchema, configurations: [setConfiguration,newsConfiguration])
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}
ContentView is just a basic TabView with a tab for news and a tab for sets.
If I run the program this way the sets tab is shown correctly but switching to News fails. If I change the order of the configurations and write the one for news first like this:
modelContainer = try! ModelContainer(for: fullSchema, configurations: [newsConfiguration, setConfiguration])
then the news tab is shown correctly and switching to sets tab fails.
NewsModel and SetModel only differ in the class name
Import Foundation
import SwiftData
@Model
public class NewsModel{
public var name: String
init(name: String) {
self.name = name
}
}
Also the tab content differs only for referencing the respecting model and the name:
import SwiftData
struct NewsTab: View {
@Query private var news: [NewsModel]
@Environment(\.modelContext) private var modelContext
var body: some View {
ScrollView{
LazyVStack{
ForEach(news){actNews in
Text("Hello, News \(actNews.name)")
}
}
.onAppear {
let news = NewsModel(name: "News from \(Date())")
modelContext.insert(news)
try! modelContext.save()
}
}
}
}
The error message is "NSFetchRequest could not locate an NSEntityDescription for entity name 'NewsModel'" (and SetsModel respectively when change the order of the configuration)
Do I explicitly need to tell the modelContext which configuration it should use or is this done automatically?
I'm a little lost here and hope someone can help me.
Best regards,
Sven
Post not yet marked as solved
Like the title says, I've realised that when I try to use filter or sort on properties that aren't standard supported data types i.e. Using a transformable or a value type like an enum, I seem to be getting the following crash...
SwiftData/DataUtilities.swift:1140: Fatal error: Unexpected type for Expansion: Optional<UIColor>
Xcode expands and shows me when trying to access the wrapped value it's crashing. I'm assumung that the query property wrapper can't handle these custom data types
@Query private var items: [Item]
{
get {
_items.wrappedValue <--- Crash here
}
}
Which seems to be pointing to a transferable property in one of my models. Below are my two models i'm using.
enum Priority: Int, Codable, Identifiable, CaseIterable {
case low
case medium
case high
var title: String {
switch self {
case .low:
return "Low"
case .medium:
return "Medium"
case .high:
return "High"
}
}
var image: Image? {
switch self {
case .medium:
return Image(systemName: "exclamationmark.2")
case .high:
return Image(systemName: "exclamationmark.3")
default:
return nil
}
}
var id: Self { self }
}
@Model
final class Item: Codable {
var title: String
@Attribute(originalName: "timestamp")
var dueDate: Date
var isCompleted: Bool
var isFlagged: Bool = false
var isArchived: Bool = false
var isCritical: Bool?
var priority: Priority?
@Relationship(deleteRule: .nullify, inverse: \Category.items)
var category: Category?
@Attribute(.externalStorage)
var image: Data?
enum CodingKeys: String, CodingKey {
case title
case timestamp
case isCritical
case isCompleted
case category
case imageName
}
init(title: String = "",
dueDate: Date = .now,
priority: Priority? = nil,
isCompleted: Bool = false) {
self.title = title
self.dueDate = dueDate
self.priority = priority
self.isCompleted = isCompleted
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.dueDate = Date.randomDateNextWeek() ?? .now
self.isCompleted = try container.decode(Bool.self, forKey: .isCompleted)
self.category = try container.decodeIfPresent(Category.self, forKey: .category)
if let imageName = try container.decodeIfPresent(String.self, forKey: .imageName),
let imageData = UIImage(named: imageName) {
self.image = imageData.jpegData(compressionQuality: 0.8)
}
if let isCritical = try container.decodeIfPresent(Bool.self, forKey: .isCritical),
isCritical == true {
self.priority = .high
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(dueDate, forKey: .timestamp)
try container.encode(isCompleted, forKey: .isCompleted)
try container.encode(category, forKey: .category)
}
}
@Model
class Category: Codable {
@Attribute(.unique)
var title: String
var items: [Item]?
@Attribute(.transformable(by: ColorValueTransformer.self))
var color: UIColor?
init(title: String = "",
color: UIColor) {
self.title = title
self.color = color
}
enum CodingKeys: String, CodingKey {
case title
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.color = UIColor(possibleColors.randomElement()!)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
}
}
And below is an example of me sorting based on my enum (Priority) & Relationship (Category name)
func sort() -> [SortDescriptor<Item>]{
switch self {
case .title:
[SortDescriptor(\Item.title)]
case .date:
[SortDescriptor(\Item.dueDate)]
case .category:
[SortDescriptor(\Item.category?.title)]
case .priority:
[SortDescriptor(\Item.priority?.rawValue)]
}
}
And a filter example below creating a predicate that we will execute to return and matches found in the title or category title
let highPriority = Priority.high
if let query {
return #Predicate {
$0.priority == highPriority &&
($0.title.contains(query) || $0.category?.title.contains(query) == true) &&
$0.isArchived == false
}
}
I'm pretty sure this is a SwiftData bug since when using strings, bools and dates it's all fine using anything outside of that box causes these crashes...
Post not yet marked as solved
I have a WidgetExtension using SwiftData (same ModelContainer setup used in the WatchApp:
@main
struct Watch_Widget: Widget {
let kind: String = "Watch_Widget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
Watch_WidgetEntryView(entry: entry)
.modelContainer(for: [Model1.self, Model2.self])
}
.configurationDisplayName("App")
.description("descriptions")
}
}
Here's the same model used in the widget view:
struct Watch_WidgetEntryView : View {
@Query var models: [Model1]
var body: some View {
let gaugeVal = models.count
Gauge(value: gaugeVal,
in: 0...60) {
Text("min")
} currentValueLabel: {
Text(String(Int(gaugeVal)))
}
.gaugeStyle(.accessoryCircular)
.widgetLabel {
Text("\(models.count) models")
}
.containerBackground(.fill.tertiary, for: .widget)
}
}
When I build the WidgetExtension, the following error was thrown and the CK data isn't loaded properly:
CloudKit setup failed because there is another instance of this persistent store actively syncing with CloudKit in this process.
Post not yet marked as solved
Did anyone successfully used transformable in SwiftData to store UIColor or SwiftUI Color type?
@Attribute(.transformable) var color: UIColor
Post not yet marked as solved
I have created an actor for the ModelContainer, in order to perform a data load when starting the app in the background. For this I have conformed to the ModelActor protocol and created the necessary elements, even preparing for test data.
Then I create a function of type async throws to perform the database loading processes and everything works fine, in that the data is loaded and when loaded it is displayed reactively.
actor Container: ModelActor {
nonisolated let modelContainer: ModelContainer
nonisolated let modelExecutor: ModelExecutor
static let modelContainer: ModelContainer = {
do {
return try ModelContainer(for: Empleados.self)
} catch {
fatalError()
}
}()
let context: ModelContext
init(container: ModelContainer = Container.modelContainer) {
self.modelContainer = container
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
self.context = context
Task {
do {
try await loadData()
} catch {
print("Error en la carga \(error)")
}
}
}
}
The problem is that, in spite of doing the load inside a Task and that there is no problem, when starting the app it stops responding the UI while loading to the user interactions. Which gives me to understand that actually the task that should be in a background thread is running somehow over the MainActor.
As I have my own API that will provide the information to my app and refresh it at each startup or even send them in Batch when the internet connection is lost and comes back, I don't want the user to be continuously noticing that the app stops because it is performing a heavy process that is not really running in the background.
Tested and compiled on Xcode 15 beta 7.
I made a Feedback for this: FB13038621.
Thanks
Julio César
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
In one of the WWDC 2023 videos a presenter said in passing that you can connect SwiftData to whatever complicated setup you have. I haven't been able to find any documentation or connection points for this though.
Obviously I can fetch the data myself and call context.insert(...) for every item, but what about refreshes, deletes, inserts, updates? Where can I wire up these external calls and their responses?
Thanks!
Post not yet marked as solved
If I make model changes in the mainContext using a batch operation like context.update OR on a background thread ModelContext ModelContext(container), my saved changes are not automatically reflected in the UI (a List of models).
Also ModelContext.willSave and ModelContext.didSave don't seem to get called.
Will automatic UI updates work in an upcoming Beta but are NOT in Beta 1?
Or will we refresh View/@Query based on a ModelContext.didSavenotification?
Or should this be working in Beta 1 and I am missing how it works?
Post not yet marked as solved
Hi,
say in my model I have members and each member optionally can have a relationship to a Club. So the relationship in the Member entity would be modelled like so:
@Relationship(.nullify, inverse: \Club.members) var club: Club?
Now I would like to fetch al Members with no Club relationship. I would assume that this would work with a predicate like this:
let noClubPred = #Predicate<Member> { member in
member.club == nil
}
Unfortunately this gives me the following error when compiling:
Generic parameter 'RHS' could not be inferred.
Has anybody an idea how to phrase this predicate correctly, or is this a beta issue and it should actually work?
Thank you!
Cheers, Michael
Post not yet marked as solved
Hi,
I am trying my first SwiftData migration, but my custom migration stage never gets called.
Since I am not sure if this is a bug with the current beta of if I am "holding it wrong" I was wondering, if anybody got migration working (their MigrationStage.custom called)? Would be great, if you could just let me know, if you got it working or running into the same issue! :-)
Thank you!
Cheers, Michael
Post not yet marked as solved
In a SwiftUI view it is easy to pull mainContext out of the Environment (thanks for that).
I have two questions:
(1) In Nick's code he shows:
let context = self.newSwiftContext(from: Trip.self)
can you say more about this method newSwiftContext(). How should we implement it to fetch a new context from the same container?
(2) In Core Data we usually read from the main context but write to a child of main context off the main thread and then the parent is notified it needs to refresh. How do we do that with SwiftData or is that managed for us?
Thanks,
Daniel
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
There is a new Relationship macro in Xcode Beta 6. The macro includes two new arguments: minimumModelCount and maximumModelCount. I wonder if anyone knows what these values are for and if there is another change under the hood.
Relationship(
_ options: PropertyOptions...,
deleteRule: Schema.Relationship.DeleteRule = .nullify,
minimumModelCount: Int? = 0,
maximumModelCount: Int? = 0,
originalName: String? = nil,
inverse: AnyKeyPath? = nil,
hashModifier: String? = nil
)
Post not yet marked as solved
I have two models a Person and a Possession
the Person model has a one to many relationship to the Possession model.
meaning each possession can only have one person but a person can have multiple possessions.
I have set my model like the following
Person:
@Model
class Person {
@Attribute(.unique)
let personID: String
@Relationship(.cascade, inverse: \Possession.person)
var possetions: [Possession]?
init(id: String, possessions: [Possession]) {
self.personID = id
self.possetions = possessions
}
}
Possession:
@Model
class Possession {
@Attribute(.unique)
let id: String
let name: String?
var person: Person?
init(id: String, name: String, person: Person) {
self.id = id
self.name = name
self.person = person
}
}
If i set a breakpoint i see that all the posessions are loaded into the memory this is something i do not want to happen.
In Core Data we get a relationship fault however, i am not seeing the same behavior in SwiftData.
here's how my view is implemented
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@EnvironmentObject private var navigationStore: NavigationStore
@Query() private var people: [Person]
var body: some View {
List {
ForEach(people) { person in
NavigationLink(value: person) {
VStack {
Text(person.personID)
}
}
.swipeActions {
Button("Delete") {
modelContext.delete(person)
}
}
}
}
.toolbar(content: {
Button("Add") {
let newPErson = Person(id: UUID().uuidString, possessions: [])
modelContext.insert(newPErson)
do {
try modelContext.save()
} catch {
assertionFailure("\(error)")
}
}
})
.navigationDestination(for: Person.self) { person in
Text("hello")
}
}
}
at the launch i do not want posessions to be loaded into the memory. I want them loaded when they are being used.
Post not yet marked as solved
I've noticed when using @Model instead of @Observable that it bumps the CPU-usage of the app with 10%, since I'm reading values on every frame of render. This seems to cause a deep lookup. Is there a way to read the objects/attributes in memory somehow?
Post not yet marked as solved
How do I perform a search using SwiftData within an observable data model
For example: If I have a database of books that contains the property isbn, how would I perform a search to confirm I either had or did not have a book with the isbn I am searching for?
Using CoreData I use a fetch request:
let predicate = NSPredicate(format: "%K == %@", #keyPath(Book.isbn), isbn)
let fetchRequest: NSFetchRequest<Book> = Book.fetchRequest()
...
let book = try context.fetch(fetchRequest). // returns [book] use isEmpty for not found.
...
if book.isEmpty {
saveBook()
}
I have a number of queries I perform on the database to drive the logic within the app, this is just one example.
Post not yet marked as solved
I have a little question that I do not dare to ask because it is so trivial.
can SwiftData actually handle compound keys? like other databases have been doing for over 30 years? i would be very disappointed if this was still not possible....
Post not yet marked as solved
Problem
Trying out SwiftData, I attempted to write unit tests for some models I was writing, but
got a crash when testing appending to a collection.
Context
Here's a sample reminiscent of the set up I have:
// Foo.swift
import SwiftData
@Model
final class Foo {
@Relationship(.cascade) private(set) var barCollection: [Bar] = [Bar]()
...
func append(bar: Bar) {
defer {
// some logic here
}
barCollection.append(bar)
}
}
// Bar.swift
import SwiftData
@Model
final class Bar {
var value: Int
init(value: Int) {
self.value = value
}
}
In my test, I'm attempting to append to the Bar array, and ensure that my append(bar:) method is doing it's job:
// FooTests.swift
@testable import FooApp
import XCTest
final class FooTests: XCTestCase {
func testAppendBar() {
let foo = Foo(bar: [])
foo.append(bar: Bar(value: 0))
XCTAssertFalse(foo.barCollection.isEmpty)
// Assertions for logic enclosed in `defer`
. . .
}
}
However, my test crashes on the assertion with EXC_BREAKPOINT, claiming issues with the getter for the array. Not much more info is given beyond that, except the stack trace showing that it crashed after attempting to assertion.
Testing with sample code
Using the project found here: https://developer.apple.com/documentation/coredata/adopting_swiftdata_for_a_core_data_app
I attempted to write unit tests for the Trip object attempting something similar with the BucketList array, and also seeing the same crash. Note: I did have to meddle with the project a bit to get it to compile since it wasn't compiling from the get go because of issues with the sample data.
Final question
Why am I not able to directly access the array holding persisted models and is there a way to test this properly?
My hunch is that since it's persisted, it's not a straightforward process anymore to retrieve the array, and if that's the case, I'm not sure why. Looking at the sample project linked above, the bucketList array is retrieved perfectly fine in the view it's being displayed in, so I know there's a lapse in my understanding of SwiftData and the appropriate setup for unit testing.
Software Used
Xcode 15.0 beta 2 • iOS 17 Simulator
Post not yet marked as solved
I have an app that uses CoreData and I want to migrate to SwiftData. After following the Migrate to SwiftData session, I only need to point to my old Core Data file to read the old data and convert it to the new SwiftData format.
My question is how do I do this? Maybe worth mentioning is that my NSPersistentContainer(name: "Model") is different to my app name.
Possible Solution?
According to a Tweet by Donny Wals this is done this way:
By default a SwiftData ModelContainer will create its underlying storage in a file called default.store. If you want to change this so you can use an existing Core Data SQLite file, you can point your container to that file instead:
// point to your old sqlite file
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
modelContainer = try ModelContainer(for: [
Movie.self
], config)
My Tested Code
@main
struct SwiftData_TestApp: App {
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
let modelContainer = try ModelContainer(for: [
Item.self
], config)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}
The problem here is that I don’t get it to work in the main app struct. When using this the way described in Dive deeper into SwiftData (at 6:58) I only get the error: Cannot use instance member 'url' within property initializer; property initializers run before 'self' is available
PS: There seems to be an issue with this WWDC session method anyway – see this post.