💊
SwiftDataで 1:N のときに配列を使うが、配列に見えて実は順番を保持しない
環境
Sequoia 15.0.1
Xcode 16.0
動作確認はシミュレータ iPhone 16 iOS18.0
問題
SwiftDataで1:Nのデータベースを作る時に次のように配列を使うと思う
@Model
class A {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .cascade)
var b: [B] = [] //⭐️ここで相手のBを保持するのに配列を使っている
init(name: String) {
self.name = name
}
}
@Model
class B {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .nullify, inverse: \A.b)
var a: A?
init(name: String) {
self.name = name
}
}
しかし次の部分で aa.b
から取り出される順番は aa.b
に追加した順番ではない。一度アプリを閉じてから上げ直すと現象がよく出る。
@Query var a: [A]
if let aa = a.first {
ForEach(aa.b) {
Text("\($0.name)") //⭐️バラバラな順番
}
}
対策
私の場合、幸いBの中に表示順を設定する order
を入れても(あるAから独り占めされても)大丈夫な設計だったので、Bに order
を入れ、表示する際はこの order
によって並べ替えるようにする。
並べ替えは、
- 自分で表示用のデータを作成して並べ替える
- SwiftDataのQueryに任せる
があると思うがSwiftDataに任せる方にしてみた。
@Model
class B {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .nullify, inverse: \A.b)
var a: A?
var order: Int = 0 //⭐️orderを追加
init(name: String) {
self.name = name
}
}
この order
は配列に追加する時に設定したり、ユーザーの並べ替え操作などで設定されるもの。
//ContentViewのbody内で
if let aa = a.first {
ContentView2(id: aa.id)
}
ContentView2
に切り出した。 ContentView
の body
から呼び出して ContentView2
の init
でQueryを確定させる。
これは単に、Queryを動的に変化させるやり方がわからなかったので。
struct ContentView2: View {
@Environment(\.modelContext) var modelContext
@Query var displayedB: [B]
init(id: String) {
_displayedB = Query(filter: #Predicate<B> { $0.a?.id == id }, sort: \B.order)
}
var body: some View {
Section {
ForEach(displayedB) {
Text("\($0.name)")
}
}
}
}
自分で表示用のデータを作成して並べ替えるのに比べて、このやり方はQueryの処理が余計に走ると思うので、そこには注意したい。
上がばらばら
下がそろっている様子
Discussion