ToDoアプリ作成を通して、CoreDataというものを勉強し、見様見真似でコードは書けるようになりました!
ただそれだけだと再現性がないので、色々調べて自分の言葉で解釈してみました!下に全コードを記載しており、CoreDataのコード解釈は //コメント 部分に記載しました!
同じくSwift初心者で、CoreDataでつまづいてしまっている方の参考になれば幸いです!
また、もし間違っている部分等ありましたら、コメントでご指摘いただけるとありがたいです!
DataModel.xcdatamodeld
Entity:ToDo
Attribute:(title: String, completed: Boolean, timestamp: Date)
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)
}
}
}
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()
}
}
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()
}
}
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()
}
}
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)")
}
})
}
}