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...
Model your schema with SwiftData
RSS for tagDiscuss the WWDC23 Session Model your schema with SwiftData
Posts under wwdc2023-10195 tag
6 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
It's been frustrating to solve this error. My iOS device and Xcode are fully updated. I can easily run app on simulator, but issue happens on my iPhone.
dyld[23479]: Symbol not found: _$s9SwiftData12ModelContextC6insert6objectyx_tAA010PersistentC0RzlFTj
Referenced from: <6FC773BB-E68B-35A9-B334-3FFC8B951A4E> Expected in: /System/Library/Frameworks/SwiftData.framework/SwiftData
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
I am trying to run a lightweight migration in which I am changing the name of a model property from name to title. The database is already populated with few records. Those records must be preserved.
Here is my schema versions:
enum TripsSchemaV1: VersionedSchema {
static var versionIdentifier: String? = "Initial version"
static var models: [any PersistentModel.Type] {
[Trip.self]
}
@Model
class Trip {
var name: String
init(name: String) {
self.name = name
}
}
}
enum TripsSchemaV2: VersionedSchema {
static var versionIdentifier: String? = "name changed to title"
static var models: [any PersistentModel.Type] {
[Trip.self]
}
@Model
class Trip {
@Attribute(originalName: "name") var title: String
init(title: String) {
self.title = title
}
}
}
Migration plan:
enum TripsMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[TripsSchemaV1.self, TripsSchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: TripsSchemaV1.self, toVersion: TripsSchemaV2.self)
}
And finally the usage:
@main
struct TripsApp: App {
let container: ModelContainer
init() {
do {
container = try ModelContainer(for: [Trip.self], migrationPlan: TripsMigrationPlan.self, ModelConfiguration(for: [Trip.self]))
} catch {
fatalError("Could not initialize the container.")
}
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
}
}
}
When I run the app, all my data for the Trips is gone and I get the following message on the output window.
Unresolved error loading container Error Domain=NSCocoaErrorDomain Code=134504 "Cannot use staged migration with an unknown coordinator model version." UserInfo={NSLocalizedDescription=Cannot use staged migration with an unknown coordinator model version.}
Any ideas?
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
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.