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 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
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...