LoginSignup
0
2

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

Last updated at Posted at 2023-08-12

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)")
            }
        })
    }
}
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2