🤝

SwiftDataの@Relationshipの使い方

2024/10/20に公開

SwiftDataって何?

こちらの記事が大変参考になります!
https://zenn.dev/yumemi_inc/articles/2a929c839b2000#relationship-のプロパティ

@Relationship

Model同士を自動で連携してくれるらしいので、便利そう。
https://developer.apple.com/documentation/swiftdata/defining-data-relationships-with-enumerations-and-model-classes/

使い方

今回は実装例として、Todoアプリを想定したコードを記載します。

Modelの作成

@Model
final class Task: Identifiable {
    @Attribute(.unique) var id: UUID
    var name: String
    // @Relationshipをつけて明示的に繋げる 
    @Relationship(inverse: \ChildTask.parent)
    var childTasks: [ChildTask]

    init(
        id: UUID = .init(),
        name: String,
        childTasks: [ChildTask] = []
    ) {
        self.id = id
        self.name = name
        self.childTasks = childTasks
    }
}

@Model
final class ChildTask: Identifiable {
    var name: String
    var isDone: Bool

    var parent: Task

    init(
        name: String,
        isDone: Bool = false,
        parent: Task
    ) {
        self.name = name
        self.isDone = isDone
        self.parent = parent
    }
}

Modelの初期化

@main
struct SampleTodoApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Task.self,
            ChildTask.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

Viewの作成

tasksにデータを保存する部分は、今回の@Relationshipを説明する上では省略させていただきます。

↓すでにtasks: [Task]に一つ以上データが入っている想定

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query var tasks: [Task]

    var body: some View {
        VStack {
            List {
                ForEach(tasks, id: \.id) { task in
                    // サンプルのためnilを考慮していません
                    let childTask = tasks.childTasks.first
                    HStack {
                        Text(tasks.name)
                        Text("\(childTask?.isDone)")
                        Spacer()
                        Button(action: {
                            onTapButton(task: task)
                        }) {
                            Text("更新")
                        }
                    }
                }
            }
        }
    }

    private func onTapButton(task: Task) {
        let isCompleted = true
        // または let isCompleted = false
        let newChildTask = ChildTask(
            name: "子タスク",
            isCompleted: isCompleted,
            // ここで紐付ける!!
            task: task
        )
        modelContext.insert(newChildTask)
        do {
            try modelContext.save()
        } catch {
            // エラー処理
        }
    }
}

さいごに

不備・間違い等あればご指摘いただけると幸いです。

Discussion