Xcodeで、既存のプロジェクトにCore Dataを追加した

## はじめに

iOSアプリを途中まで作っていて、あとから`Core Data`を追加した。

- `Core Data`をつかうこと自体がはじめて
- `Core Data`を後から追加するのはいろいろやることがある

の2点でハマったけれど、なんとかなったので記録する。


## 環境

下記のバージョンで実施。

- iOS 14.4
- Xcode 12.4
- macOS BigSur 11.1


## 主な結論

- 設定値の保管は`UserDefaults`もしくは`AppStorage`でよいが、あるていどのデータになると`Core Data`をつかうべき

- CoreDataの構成を変えたとき（`Entity`もしくは`Attribute`）は、アップデートする必要がある

- `@EnvironmentObject`を使うときとは違って、`@Environment(\.managedObjectContext)`を使ってFetchしたデータの、要素を`ForEach`で`View`にわたすと、値渡しではなく参照渡しになる


## 手順

### `Core Data`の追加

- すでにプロジェクトがあって、`Core Data`を追加したい場合、`File > New > File...`で`CoreData > Data Model`を選択

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184128/ad0a0152-dd2c-23cc-41e2-b8806c66d00b.png" width=50%>


- 次に、`Entity`と`Attribute`を追加する。

- クラス定義ファイルの生成
    - `Entity`の`Inspector > Data Model Inspector`パネルで、`Entity > Codegen`を`Manual/None`に変更
    - メニューから`Editor > Create NSManagedObject subclass...`を選択し、クラス定義ファイルを生成する

`Core Date`のデータベース（っていうのかな？）の追加はこれで完了。


### `Core Data`のデータベースのアップデート時の操作

- まず、メニューから`Editor > Add Model Version...`をクリックし、指示に従う

- 次に、`Inspector`の`File Inspector > Core Data Model > Model Version`で新しく作ったものを選んでおく

これをしておかないと、アプリがクラッシュする（した）。どうやら、データベースの`Entity`や`Attribute`を変更したら、これをやっておかないといけない。

### リソース

ここまでについて、画像入りの解説記事は、たとえば下記に詳しい：
[iOSにユーザーデータを保存する方法と、そのためのコードの書き方: UserDefaults、Core Data、Key Chain、CloudKit - Qiita](https://qiita.com/MaShunzhe/items/e7263507e07919a6adab)

> プロジェクト作成時に、Life Cycleで`SwiftUI App`を選択していると、`AppDelegate.swift`が存在しない。上記記事で、`AppDelegate.swift`に関する箇所は読み飛ばした。代わりにやったことは、下記に記す。


### 追加するコード

いろいろ試したが、けっきょく「`Core Data`つきの新規プロジェクトをつくって、ファイルや要素をコピーしてくる」のが手間が少ない気がする。

#### `Persistence.swift`のコピーと修正

- 新規プロジェクトを作成し、その際に`Use Core Data`にチェックを入れる

- `Persistence.swift`をコピーしてくる

手で変更を要する箇所は、

```swift
static var preview: PersistenceController = {
    let result = PersistenceController(inMemory: true)
    let viewContext = result.container.viewContext
    for _ in 0..<10 {
        let newItem = Item(context: viewContext)
        newItem.timestamp = Date()
    }
```
の`newItem.timestamp = Date()`のところ。`.timestamp`と`Date()`をじぶんのデータベースのAttributeにあわせる。ここは、プレビュー用のテストデータセットなので、値は適当でよい。

それと、

```swift
init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "anotherNewCoreData")
    if inMemory {
        container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
    }
```
の`(name: "anotherNewCoreData")`を、じぶんのデータベースのEntityの名前にあわせる。

#### `@main`について

- 新規プロジェクトをパクる。具体的には

```swift
let persistenceController = PersistenceController.shared
```

```swift
ContentView()
    .environment(\.managedObjectContext, persistenceController.container.viewContext)
```
の2箇所。

#### `Content View`について

- ここからは個別対応になってきて一般化しがたいが、主には下記。

```swift
@Environment(\.managedObjectContext) private var viewContext

@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
    animation: .default)
private var items: FetchedResults<Item>
```
のところ。`@Environment`を宣言し、`@FetchRequest`で取りかたを指定し、`items`に格納する。

> `Items`はEntityの名称に置き換え、`items`は自分の使いたい変数名にした。

```swift
private func addItem() { ... }
private func deleteItems(offsets: IndexSet) { ... }
```

のところは、呼び方から書き方（`Core Data`への保存のしかた）まで、流用した。

```swift
static var previews: some View {
    ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
```

はプレビューの使いかた。

> 小ビューに取得したデータベースのレコードをわたす方法が未だにわからない...


### データのわたしかたと操作

親ビューでたとえば

```swift
NavigationView{
    List {
        ForEach(records) {record in
            RecordRow(record: record)
        }
    }
```
と、`record`を下層ビューに渡したとする。受け取った下層ビューは

```swift
Image(systemName: "checkmark.seal")
    .onTapGesture {
        record.isDone.toggle()
```
と書くだけで、タップするトグルされる。

`@EnvironmentObject`を使って`records`を共有した場合、`ModelData.records`はたしかに共有されているが、ビューに渡した`record`は共有されていない（値渡し）になるので、

```swift
ModelData.records[index].isDone.toggle()
```
などとしないといけない。ところが、`@Environment(\.managedObjectContext)`の場合は、参照渡しになっているので、直接書き換えられる。

> と理解しているのだけれど違っていたらご指摘ください……


### 保存のおまけ

新規プロジェクトからの流用ではないが、下記をやっておくとたしかに便利。

> ホームボタンを押したときや、アプリを終了したときに、`Core Date`に保存しておいてくれる

`Persistence.swift`に

```swift
struct PersistenceController {

    ...
    
    func save() {
        let context = container.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Show some error here
            }
        }
    }
}

```

と書いておき、`@main`で

```swift
@main
struct xxxxxxApp: App {
    let persistenceController = PersistenceController.shared
    @Environment(\.scenePhase) var scenePhase

    var body: some Scene {
        WindowGroup { ... }
        .onChange(of: scenePhase) { _ in
            persistenceController.save()
        }
    }
}
```

これは
[How to configure Core Data to work with SwiftUI](https://www.hackingwithswift.com/quick-start/swiftui/how-to-configure-core-data-to-work-with-swiftui)より。

> 英語だし、ちょっと説明が飛んでいるところもあるけれど、新しい情報なのですごく助かった。

### その他リソース

[Add, Delete & Save in Core Data in SwiftUI](https://medium.com/swiftui-forum/core-data-in-swiftui-6e8de7ad7c47)　にもずいぶん助けられた。

[カピ通信 - SwiftUI](https://capibara1969.com/swiftui/) もとてもわかりやすい。残念ながら、発見したのが遅かった。最初に見つけていれば、もっとラクだったのになあ！


## おわりに

けっこう悩んだけど、動くとおもしろい！！
ネット上の各種リソースに深謝🙏