Dive deeper into SwiftData

RSS for tag

Discuss the WWDC23 Session Dive deeper into SwiftData

View Session

Posts under wwdc2023-10196 tag

23 Posts
Sort by:
Post not yet marked as solved
4 Replies
841 Views
.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.
Posted Last updated
.
Post marked as Apple Recommended
1.6k Views
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
Posted Last updated
.
Post not yet marked as solved
4 Replies
1.5k Views
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...
Posted
by tundsdev.
Last updated
.
Post not yet marked as solved
2 Replies
787 Views
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.
Posted
by cvhu.
Last updated
.
Post not yet marked as solved
6 Replies
2.9k Views
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
Posted
by jcfmunoz.
Last updated
.
Post marked as solved
5 Replies
3.2k Views
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.
Posted
by Ried.
Last updated
.
Post not yet marked as solved
1 Replies
622 Views
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!
Posted
by xike.
Last updated
.
Post not yet marked as solved
4 Replies
2.2k Views
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?
Posted
by RossCoops.
Last updated
.
Post not yet marked as solved
2 Replies
839 Views
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
Posted
by milutz.
Last updated
.
Post not yet marked as solved
1 Replies
711 Views
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
Posted
by milutz.
Last updated
.
Post not yet marked as solved
3 Replies
1.8k Views
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
Posted Last updated
.
Post not yet marked as solved
4 Replies
1k Views
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
Posted
by milutz.
Last updated
.
Post not yet marked as solved
1 Replies
656 Views
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 )
Posted
by macrojd.
Last updated
.
Post not yet marked as solved
0 Replies
598 Views
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.
Posted
by lukdor.
Last updated
.
Post not yet marked as solved
0 Replies
553 Views
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?
Posted Last updated
.
Post not yet marked as solved
0 Replies
459 Views
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.
Posted
by t-j-c.
Last updated
.
Post not yet marked as solved
1 Replies
529 Views
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....
Posted Last updated
.
Post not yet marked as solved
1 Replies
1.1k Views
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
Posted
by pereyra.
Last updated
.
Post not yet marked as solved
1 Replies
1.5k Views
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.
Posted Last updated
.