🗂
Notionのようなデータ構造をSwiftDataで作成する
Notionのような階層構造のブロックを内部で持つデータ構造をSwiftDataを使って表現します
(こういうやつ)
これをNavigationStackにマッピングして閲覧できるようにします
モデル: Item.swift
@Relationship
を使って自己参照リレーションを張ります
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
var content: String
var parent: Item?
init(content: String, parent: Item? = nil, timestamp: Date? = nil) {
self.content = content
self.parent = parent
self.timestamp = timestamp ?? Date()
}
@Relationship(deleteRule: .cascade, inverse: \Item.parent)
var children: [Item] = [Item]()
}
Top: ContentView.swift
親を持たないルートブロックを描画するView
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query(filter: #Predicate<Item> { item in
item.parent == nil
}, sort: \.timestamp, order: .forward)
private var items: [Item]
var body: some View {
NavigationStack {
List {
ForEach(items) { item in
NavigationLink {
ContentDetailView(item: item)
} label: {
Text(item.content)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
.navigationTitle("Top")
}
}
}
オプション: addItem()
テストデータをインサートするボタンを実装します
private func addItem() {
withAnimation {
let newItem = Item(content: "Parent")
let child = Item(content: "Child", parent: newItem)
let grandchild = Item(content: "Grandchild", parent: child)
modelContext.insert(newItem)
modelContext.insert(child)
modelContext.insert(grandchild)
modelContext.insert(Item(content: "Grandchild 2", parent: child))
modelContext.insert(Item(content: "Grandchild 3", parent: child))
}
}
オプション: deleteItems()
削除ボタンのハンドリング実装
@Relationship(deleteRule: .cascade, inverse: \Item.parent)
を設定しているので下位のブロックがまとめて削除されます
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
下位ブロック: ContentDetailView.swift
import SwiftUI
import SwiftData
struct ContentDetailView: View {
var item: Item
var body: some View {
List {
ForEach(item.children) { item in
NavigationLink {
ContentDetailView(item: item)
} label: {
Text(item.content)
}
}
}.navigationTitle(item.content)
}
}
SQLiteの内部データをデバッグする
SwiftDataはCore Dataスキーマを継承していて、シミュレータの場合は以下にSQLiteファイルがあります
find ~/Library/Developer/CoreSimulator/Devices -name default.store
sqlite3 ~/Library/Developer/CoreSimulator/Devices/**/data/Containers/Data/Application/**/Library/Application Support/default.store
sqlite> .tables
ACHANGE ATRANSACTIONSTRING Z_METADATA Z_PRIMARYKEY
ATRANSACTION ZITEM Z_MODELCACHE
sqlite> SELECT * FROM "ZITEM" ORDER BY "Z_PK" LIMIT 300 OFFSET 0;
6|1|1||726376858.878182|Parent
7|1|1|8|726376858.879967|Grandchild 2
8|1|1|6|726376858.878591|Child
9|1|1|8|726376858.87864|Grandchild
10|1|1|8|726376858.880036|Grandchild 3
11|1|1||726376859.655562|Parent
12|1|1|15|726376859.656266|Grandchild 2
13|1|1|15|726376859.656499|Grandchild 3
14|1|1|15|726376859.655821|Grandchild
15|1|1|11|726376859.6557|Child
16|1|1|17|726376860.290472|Grandchild 2
17|1|1|18|726376860.289769|Child
18|1|1||726376860.289574|Parent
19|1|1|17|726376860.289951|Grandchild
20|1|1|17|726376860.290769|Grandchild 3
Discussion