New to StackOverflow here and still learning swift. Just thought I'd share the solutions I found (both solutions are working for my use case so far, but do let me know if they don't work for other scenarios).
Solution #1: The model's built-in persistentModelID contains a "url" which appears to be incremented when a model is created and saved (e.g. x-coredata://xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Model2/p1). Not sure what it's for but the counter stays the same even if another model is deleted.
@Query(sort: \Model2.persistentModelID) var model2s: [Model2]
This is simplest and one can sort at the model level (which is supposed to be the fastest).
Solution #2: Replicating ordered array behaviour by saving another array containing order information in Model1 (Model2 doesn't need to know the order information, which is what my use case requires). Still trying to tidy up the code, but remove/insert/append functions seems to be working so far.
import SwiftData
@Model
class Model1 {
private var model2sUnsorted: [Model2] // <== actual model
private var model2Orders: [Model2Order] = []
var model2s: [Model2] {
get {
model2sUnsorted.ordered(by: model2Orders.sorted(by: <).map({ $0.id }))
}
set {
model2sUnsorted = newValue
model2Orders = newValue.map({ Model2Order(id: $0.id) }).ordered()
}
}
init() {
self.model2sUnsorted = []
}
}
@Model
class Model2 {
var name: String
var model1: Model1?
init(name: String = "", model1: Model1? = nil) {
self.name = name
self.model1 = model1
}
}
// MARK: - Reordering with another array: see https://stackoverflow.com/questions/43056807/sorting-a-swift-array-by-ordering-from-another-array
extension Model2: Reorderable {
typealias OrderElement = PersistentIdentifier?
var orderElement: OrderElement { id }
}
protocol Reorderable {
associatedtype OrderElement: Equatable
var orderElement: OrderElement { get }
}
extension Array where Element: Reorderable {
func ordered(by preferredOrder: [Element.OrderElement]) -> [Element] {
sorted {
guard let first = preferredOrder.firstIndex(of: $0.orderElement) else {
return false
}
guard let second = preferredOrder.firstIndex(of: $1.orderElement) else {
return true }
return first < second
}
}
}
// MARK: - Saving the Order
struct Model2Order: Codable, Comparable, Ordered {
var id: PersistentIdentifier
var order: Int?
static func < (lhs: Model2Order, rhs: Model2Order) -> Bool {
guard let first = lhs.order else { return false }
guard let second = rhs.order else { return true }
return first < second
}
}
protocol Ordered {
var order: Int? { get set }
}
extension Array where Element: Ordered {
func ordered() -> [Element] {
var arr = self
for index in arr.indices { arr[index].order = index }
return arr
}
}
In the view, can do this:
Button("Add") {
let newModel2 = Model2()
modelContext.insert(newModel2)
do {
try modelContext.save() // <== CAVEAT: must save before manipulating the array, because the id seems to be different before and after saving
} catch {
print(error.localizedDescription)
}
if let siblings = model2.model1?.model2s,
let index = siblings.firstIndex(of: model2) {
let nextIndex = siblings.index(after: index)
model2.model1?.model2s.insert(newModel2, at: nextIndex)
do {
try modelContext.save()
} catch {
print(error.localizedDescription)
}
}
}
@Relationshipwhen two models are connected.@Relationshipis only required to specify a custom deletion rule. "When a model contains an attribute whose type is also a model (or a collection of models), SwiftData implicitly manages the relationship between those models for you. By default, the framework sets relationship attributes to nil after you delete a related model instance. To specify a different deletion rule, annotate the property with the Relationship(::originalName:inverse:hashModifier:) macro. " When I add @Relationship`to the model, there is no difference to the outcome.Setfor to-many relationships properties so that is most likely why your array property changes order all the time.TestModel2if you have a query onTestModel?