【新人プログラマ応援】この素晴らしいSwiftUIにCoreDataを！【CoreData読み書き編】

こんにちは．
Sadmachineです．

この記事ではアプリ開発の基礎...
__CoreDataの読み書きをわかりやすく解説していきます！__
スクリーンショットも適宜つけるので参考にしてください！

前回の記事では
Xcodeプロジェクトの開始〜CoreDataの初期設定までをわかりやすく解説しました！
前回の記事：[この素晴らしいSwiftUIにCoreDataを！【初期設定編】](https://qiita.com/Sadmachine/items/d945576e9ccf561e7f02)

__LGTMたくさんしてね！！！__

## 目次
<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=3 orderedList=false} -->
<!-- code_chunk_output -->
- [おさらい：0. 開発環境](## 0. 開発環境)
- [3. CoreDataの読み書きをしてみよう](## 3. CoreDataの読み書きをしてみよう)
- [まとめ](## まとめ)

<!-- /code_chunk_output -->

## おさらい ： 0. 開発環境
筆者の開発環境をめっちゃ簡単に紹介します．

- Xcode 12.3
- SwitfUI

以上．
もっと詳しく知りたい人はTwitterに凸して，どうぞ．

## 3. CoreDataの読み書きをしてみよう
前回の記事：[この素晴らしいSwiftUIにCoreDataを！【初期設定編】](https://qiita.com/Sadmachine/items/d945576e9ccf561e7f02)の設定のままではエラー吐かれがちなので修正します．
※以下のソースのMugenMemoは適宜自分のProductNameに読み替えて進めてください．

### 3-1 CoreDataを動かすためのエラー修正
以下の2つのコードを修正します．
Persistence.swift　（絶対修正が必要）
MugenMemoApp.swift　（場合によっては修正が必要）
ほとんどの場合，Persistence.swiftの修正のみで済むと思います．

#### Persistence.swiftの修正
<img width="700" alt="スクリーンショット 2021-03-22 23 18 32" src="https://user-images.githubusercontent.com/65950135/112072894-3554f100-8bb6-11eb-9dcf-8e0ce1621ed4.png">
↑図の16~19行目を削除またはコメントアウトするだけです．

Persistence.swiftは以下のようにすればOK．

```Persistence.swift
import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
//
//ここら辺にあるfor文をコメントアウトにするか削除
//
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "MugenMemo")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                Typical reasons for an error here include:
                * The parent directory does not exist, cannot be created, or disallows writing.
                * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                * The device is out of space.
                * The store could not be migrated to the current model version.
                Check the error message to determine what the actual problem was.
                */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}
```

#### MugenMemoApp.swiftの修正
もし，ProductNameApp.swiftでエラーを吐かれたら
.environment(\.managedObjectContext, persistenceController.container.viewContext)
の部分に色が付いていないことが原因であることが多いです．
.environmentの1行を切り取り，元あった場所に貼り付ければ.environment部分に色がつき，大体解決します．

一応コードも以下に載せておきます．

``` MugenMemoApp.swift
import SwiftUI

@main
struct MugenMemoApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup{
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}
```
以上でエラーを吐かないで動いてくれるようになります．

### 3-2 CoreDataにデータを追加
流れとしては
1. __CoreDataにデータを書き込む準備__
→ SwiftUIでCoreDataにデータを追加する「＋ボタン」を実装
2. __CoreDataにデータを書き込む・読み込んで表示させる__
→ ＋ボタンを押すとCoreDataにデータを追加する処理を実装
→ ContentView.swiftにList形式でCoredataの中身を表示させる処理の実装
3. __エミュレーターでテストして動作を確認する__

それではやっていきましょう．

#### CoreDataにデータを書き込む準備
→ SwiftUIでCoreDataにデータを追加する「＋ボタン」を実装


この時点では押しただけでは何も起きないです．
もちろんCoreDataにも何も追加されません．
ContentView.swiftを以下のように編集します．

```ContentView.swift
import SwiftUI
import CoreData

struct ContentView: View {
    // 編集箇所①
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Memo.title, ascending: true)],animation: .default) var Data: FetchedResults<Memo>
    
    var body: some View {
        NavigationView {
            List {
                Text("TEST")
            }
            // 編集箇所②
            .navigationTitle("Test")
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(trailing: Button(action: {
                                    // ここにアクションを書く
                                }) {
                                    Image(systemName: "plus")
                                }
            )

        }
    }

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

保存してResumeを押すと...

<img width="397" alt="スクリーンショット 2021-03-23 10 33 01" src="https://user-images.githubusercontent.com/65950135/112079258-304a6e80-8bc3-11eb-86a7-d28e92e4ec57.png">
Canvas上でこうなるはずです．
こうなっていればOK！

#### CoreDataにデータを書き込む・読み込んで表示させる
→ ＋ボタンを押すとCoreDataにデータを追加する処理を実装
→ ContentView.swiftにList形式でCoredataの中身を表示させる処理の実装

以下がCoreDataに指定した文字列を追加し，Listに表示するコードです．

```ContentView.swift
import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Memo.title, ascending: true)],animation: .default) var Data: FetchedResults<Memo>
    
    var body: some View {
        NavigationView {
            List {
                // Text("TEST")をForEach文に変更．
                // CoreDataの中身を表示している，
                ForEach(Data, id: \.self) { i in
                    Text(i.title!)
                }
            }
            .navigationTitle("Test")
            .navigationBarTitleDisplayMode(.inline)
            // ＋ボタンとCoreDataにデータを追加する処理を紐づける
            .navigationBarItems(
                trailing:
                Button(action: {
                                create()
                                }) {
                    Image(systemName: "plus")
                                }
            )
        }
    }
    
    // CoreDataにデータ（今回は文字列"I LOVE Swift"）を追加する処理を実装
    func create() {
        let t = Memo(context: moc)
        t.title = "I LOVE Swift"
        do {
            try moc.save()
        } catch {

        }
    }
}

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

<img width="700" alt="スクリーンショット 2021-03-23 12 19 49" src="https://user-images.githubusercontent.com/65950135/112087858-182e1b80-8bd2-11eb-966f-4253aa43ce82.png">
↑図のようにResumeを押した時のCanvasには何も表示されていませんが問題ありません．
ここからはエミュレーターで実行してみましょう．

#### エミュレーターでテストして動作を確認する
<img width="700" alt="スクリーンショット 2021-03-23 12 19 49" src="https://user-images.githubusercontent.com/65950135/112088196-bae69a00-8bd2-11eb-9c36-bec4620bcd7d.png">
赤く囲われた部分のiPhone11というところを押すとエミュレーターの機種が選べます．
選んだら左の再生ボタンを押してエミュレーター上でビルドしてみましょう．

<img width="700" alt="スクリーンショット 2021-03-23 12 26 49" src="https://user-images.githubusercontent.com/65950135/112088374-0f8a1500-8bd3-11eb-8624-f72d1d77849a.png">
↑図のようになっていればエミュレーターの起動とアプリのビルドは成功です．
試しに＋ボタンを押してみると...

<img width="404" alt="スクリーンショット 2021-03-23 12 28 15" src="https://user-images.githubusercontent.com/65950135/112088488-42340d80-8bd3-11eb-96dc-f21f4ee970bc.png">

I LOVE Swiftという項目が追加されました．
試しにアプリを落としてみてもI LOVE Swiftは保持されたままです．
CoreDataへの追加は成功です！
やったぜ．

ちなみに，＋ボタンを連打しまくるとI LOVE SwiftがListに量産されます．

## まとめ

CoreDataの読み書きをわかりやすく解説しました．
今回はここまで．

次回は，

__CoreDataのデータの削除とSwiftUIにおける削除UIの実装__

の予定です．
SwiftUIの削除UIを導入してCoreDataのデータを削除できるようにします．
結構簡単に実装できるので次回もお付き合いください．

備忘録的なまとめですが，誰かの参考になれば嬉しいです．
少しでも参考になりましたらLGTMやフォロー，Twitterでの拡散をしてください！
喜びます．