【SwiftUI】Swift初心者が「CoreData」のコードを解釈してみた

ToDoアプリ作成を通して、CoreDataというものを勉強し、見様見真似でコードは書けるようになりました！

ただそれだけだと再現性がないので、色々調べて自分の言葉で解釈してみました！下に全コードを記載しており、CoreDataのコード解釈は //コメント 部分に記載しました！

同じくSwift初心者で、CoreDataでつまづいてしまっている方の参考になれば幸いです！

また、もし間違っている部分等ありましたら、コメントでご指摘いただけるとありがたいです！


```DataModel.xcdatamodeld
Entity：ToDo
Attribute：（title: String, completed: Boolean, timestamp: Date）
```

```swift:ToDoApp.swift
import SwiftUI
import CoreData

@main
struct ToDoApp: App {
    //PersistenceController()は、Persistent.swiftファイルで定義した構造体。PersistenceController()を記述するファイルを分けているだけで、別にToDoApp.swiftファイルの中に構造体を記述しても問題はない。
    let persistenceController = PersistenceController()
    var body: some Scene {
        WindowGroup {
            ContentView()
            //persistenceControllerは上のletで定義したPersistenceController()のインスタンス
            //persistenceController.containerBox.viewContextで、ContextPersistenceController()の中に定義されているlet containerBox: NSPersistentContainerの、viewContextにアクセスしている
            //persistenceControllerもcontainerBoxも自身で任意に定義した変数（定数）だが、viewContextはすでにSwift側で決められているNSManagedObjectContextクラス型のプロパティだから、「viewContext」と記述する必要がある
            //.environmentは指定したビューに環境変数を提供するためのインスタンスメソッド
            //\.propertyName は環境変数のキーパスであり、value はその環境変数に設定する値
                .environment(\.managedObjectContext, persistenceController.containerBox.viewContext)
        }
    }
}
```

```swift:ContentView.swift
import SwiftUI
import CoreData

struct ContentView: View {
    let list = ["やること", "やったこと"]
    @State var selectedTab = 0
    var body: some View {
        VStack {
            TabView(selection: $selectedTab) {
                ToDoView().tag(0)
                DoneView().tag(1)
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            .padding()
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

```swift:ToDoView.swift
import SwiftUI
import CoreData

struct ToDoView: View {
    //ToDoApp.swiftファイルで.environmentメソッドを設定することで、親ビューContentView()とその子ビュー（ToDoView、DoneView等）が環境変数@ Environmentを使用できるようになった
    //toDoDataは環境変数。.environmentのValueに指定したpersistenceController.containerBox.viewContextのviewContextと同名にするとわかりにくいため、今回はあえて別名toDoDataにしている。
    //.environmentのキーパスに指定した\.managedObjectContextを@ Environmentのキーパスに指定することで、環境変数toDoDataを介して.environmentのValueに設定したpersistenceController.containerBox.viewContextにアクセスできるようになる
    @Environment(\.managedObjectContext) var toDoData
    //toDoList変数は、データベースから値を取り出す(取得する)ために用いられる（逆にtoDoData変数は、データベースに値を追加、削除、更新、する際に用いる）
    //データ型にはToDoエンティティを指定している（FetchedResults<ToDo>）
    //引数sortDescriptorsには、取得したデータの並び順を指定する。ascendingがfalseのときは降順、trueは昇順。
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \ToDo.timestamp, ascending: false)]) var toDoList: FetchedResults<ToDo>
    @State var inputText = ""
    var body: some View {
        VStack {
            List {
                ForEach(toDoList) { toDo in
                    if !(toDo.completed) {
                        HStack {
                            if (toDo.completed) {
                                Text("☑")
                                    .font(.title)
                                    .foregroundColor(.gray)
                            } else {
                                Text("□")
                                    .font(.title)
                                    .foregroundColor(.gray)
                            }
                            Button {
                                completeToDo(toDo: toDo)
                            } label: {
                                Text(toDo.title!)
                            }
                        }
                    }
                }
            }
            .listStyle(.plain)
            
            Spacer()
            TextField("やることを追加", text: $inputText, prompt: Text("やることを追加"))
                .textFieldStyle(.roundedBorder)
                .onSubmit {
                    if inputText != "" {
                        addToDo()
                    }
                }
        }
    }
    
    func addToDo() {
        //ToDoエンティティのcontextにtoDoData（=persistenceControllerのcontainerBoxのviewContext）を指定している
        //するとデータが格納されているcontextにアクセスできるようになる
        //context引数で、どのcontextにアクセスするかを指定している
        let newToDo = ToDo(context: toDoData)
        newToDo.title = inputText
        newToDo.timestamp = Date()
        do {
            try toDoData.save()
        } catch {
            fatalError("セーブに失敗")
        }
        inputText = ""
    }
    
    //引数toDoのデータ型にToDoエンティティを指定している
    //つまりここにはtoDoList変数が入る
    func completeToDo(toDo: ToDo) {
        toDo.completed.toggle()
        do {
            try toDoData.save()
        } catch {
            fatalError("セーブに失敗")
        }
    }
}

struct ToDoView_Previews: PreviewProvider {
    static var previews: some View {
        ToDoView()
    }
}
```

```swift:DoneView.swift
import SwiftUI
import CoreData

struct DoneView: View {
    //ToDoView内で宣言した@ Environment(\.managedObjectContext) var toDoDataと同じ役割だが構造体が異なる（=スコープ外である）ため同じ変数が使えず、そのため新しく環境変数を宣言する必要がある
    //toDoDataとは異なる変数であることを意識するために、今回はあえてdoneDataと別の変数名を使用している
    @Environment(\.managedObjectContext) var doneData
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \ToDo.timestamp, ascending: true)]) var toDoList: FetchedResults<ToDo>
    var body: some View {
        VStack {
            List {
                ForEach(toDoList) { toDo in
                    if (toDo.completed) {
                        HStack {
                            Image(systemName: "arrow.uturn.backward")
                            Button {
                                unDoneToDo(toDo: toDo)
                            } label: {
                                Text(toDo.title!)
                            }
                        }
                    }
                }
            }
            .listStyle(.plain)
            Spacer()
        }
    }
    
    func unDoneToDo(toDo: ToDo) {
        toDo.completed.toggle()
        do {
            try doneData.save()
        } catch {
            fatalError("セーブに失敗")
        }
    }
}

struct DoneView_Previews: PreviewProvider {
    static var previews: some View {
        DoneView()
    }
}
```

```swift:Persistent.swift
struct PersistenceController {
    //ここでNSPersistentContainerクラス型のコンテナ(containerBox)を作成している
    //モデルファイルに"DataModel"を指定したNSPersistentContainerをcontainerBox内にいれることで、"DataModel"で設定したAttributeのデータ型を参照してデータの形を変え、containerBox内のcontextにデータが格納できるようになる
    let containerBox: NSPersistentContainer
    init() {
        containerBox = NSPersistentContainer(name: "DataModel")
        //context内に格納されたデータを、形を変えて格納する「PersistentStore」を生成している。その格納されたデータが、その後データベースに保存される
        containerBox.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error)")
            }
        })
    }
}
```