リレーション
データベースのリレーションとは、データベース内のテーブル間の関連性のことを指す。
関連 |
説明 |
1 : 1 (1対1) |
あるテーブルのレコードが、別のテーブルのレコードと1つだけ関連する場合 |
1 : N (1対多) |
あるテーブルの1つのレコードが、別のテーブルの複数のレコードと関連する場合 |
N : N (多対多) |
あるテーブルの複数のレコードが、別のテーブルの複数のレコードと関連する場合 |
SwiftData でリレーションを定義すると、関連があるプロパティに自動的に値が入る。
Delete Rule を定義することで削除時にリレーションがあるモデルを nil にしたり、データを削除する振る舞いも定義可能。
1 : 1(1対1)
import SwiftData
@Model
class User {
var name: String
@Relationship(deleteRule: .cascade) var profile: Profile?
init(name: String, profile: Profile? = nil) {
self.name = name
self.profile = profile
}
}
@Model
class Profile {
var age: Int
var user: User?
init(age: Int, user: User? = nil) {
self.age = age
self.user = user
}
}
1 : N(1対多)
1(タスク):N(カテゴリ)
import SwiftData
@Model
class Category {
var name: String
@Relationship(deleteRule: .cascade) var tasks: [Task] = []
init(name: String) {
self.name = name
}
}
@Model
class Task {
var title: String
var category: Category?
init(title: String, category: Category? = nil) {
self.title = title
self.category = category
}
}
N : N(多対多)
import SwiftData
@Model
class Student {
var name: String
@Relationship(deleteRule: .nullify) var courses: [Course] = []
init(name: String) {
self.name = name
}
}
@Model
class Course {
var title: String
@Relationship(deleteRule: .nullify) var students: [Student] = []
init(title: String) {
self.title = title
}
}
削除ルール(deleteRule)
deleteRule の値 |
説明 |
.cascade |
親オブジェクトが削除されたとき、関連する子オブジェクトも削除される。 |
.nullify |
親オブジェクトが削除されたとき、子オブジェクトの関連を解除(nil にする)。 |
.deny |
親オブジェクトに関連する子オブジェクトがある場合、削除を禁止する。 |
.noAction |
何もしない(制約なし)。 |
サンプル
・1(タグ)対 多(イベント)
・イベントはタグの情報をもつ(持たなくてもよい)
・タグを変更、削除するとイベントのタグ情報も連動する
【ポイント】
・イベントクラスはtagを持つにする
・タグクラスはイベントクラス(配列)を持つようにする


import SwiftUI
import SwiftData
@Model
class Event {
var eventId: UUID
var eventName: String
var tag: Tag?
init(eventName: String, tag: Tag? = nil) {
self.eventId = UUID()
self.eventName = eventName
self.tag = tag
}
}
@Model
class Tag {
var tagId: UUID
var tagName: String
var order: Int
var event: [Event] = []
init(tagName: String, order:Int) {
self.tagId = UUID()
self.tagName = tagName
self.order = order
}
}
struct ContentView: View {
@Environment(\.modelContext) var modelContext
@Query var events: [Event]
@Query(sort: [SortDescriptor(\Tag.order)]) var tags: [Tag]
@State private var newEventName: String = ""
@State private var newTagName: String = ""
@State private var changeTagName = ""
@State private var selectedTagIndex: Int?
@State private var changeTagIndex: Int = 0
@State private var alertDispayFlg = false
var body: some View {
Form{
HStack{
TextField("イベント", text: $newEventName)
Button("登録"){
if selectedTagIndex != nil {
let tag = tags[selectedTagIndex!]
let newEvent = Event(eventName: newEventName, tag: tag)
modelContext.insert(newEvent)
} else {
let newEvent = Event(eventName: newEventName)
modelContext.insert(newEvent)
}
}
.disabled(newEventName.isEmpty)
}
HStack{
TextField("タグ", text: $newTagName)
Button("登録"){
let maxOrder = tags.isEmpty ? 0 :(tags.map { $0.order }.max() ?? 0) + 1
let newTag = Tag(tagName: newTagName, order: maxOrder)
modelContext.insert(newTag)
}
.disabled(newTagName.isEmpty)
}
Section("選択中のタグ") {
if selectedTagIndex != nil {
Text(tags[selectedTagIndex!].tagName)
}
}
}
List(events) { event in
HStack {
Text(event.eventName)
Spacer()
if let name = event.tag?.tagName {
Text(name)
} else {
Text("タグなし")
}
Button("削除"){
modelContext.delete(event)
selectedTagIndex = nil
}
.buttonStyle(.plain)
}
}
List {
ForEach (tags) { tag in
HStack{
Text(tag.tagName)
.onTapGesture {
if let index = tags.firstIndex(of: tag) {
selectedTagIndex = index
}
}
Spacer()
Button("変更"){
if let index = tags.firstIndex(of: tag) {
changeTagIndex = index
}
changeTagName = ""
alertDispayFlg = true
}
.alert(tags[changeTagIndex].tagName, isPresented: $alertDispayFlg) {
TextField(tags[changeTagIndex].tagName, text: $changeTagName)
Button("変更") {
tags[changeTagIndex].tagName = changeTagName
try? modelContext.save()
changeTagName = ""
}
.disabled(changeTagName.isEmpty)
Button("キャンセル", role: .cancel){
changeTagName = ""
}
}
.buttonStyle(.plain)
Button("削除"){
modelContext.delete(tag)
selectedTagIndex = nil
changeTagIndex = 0
}
.buttonStyle(.plain)
}
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: [Event.self, Tag.self], inMemory: true)
}