はじめに
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
を選択
-
次に、
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
プロジェクト作成時に、Life Cycleで
SwiftUI App
を選択していると、AppDelegate.swift
が存在しない。上記記事で、AppDelegate.swift
に関する箇所は読み飛ばした。代わりにやったことは、下記に記す。
追加するコード
いろいろ試したが、けっきょく「Core Data
つきの新規プロジェクトをつくって、ファイルや要素をコピーしてくる」のが手間が少ない気がする。
Persistence.swift
のコピーと修正
-
新規プロジェクトを作成し、その際に
Use Core Data
にチェックを入れる -
Persistence.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にあわせる。ここは、プレビュー用のテストデータセットなので、値は適当でよい。
それと、
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "anotherNewCoreData")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
の(name: "anotherNewCoreData")
を、じぶんのデータベースのEntityの名前にあわせる。
@main
について
- 新規プロジェクトをパクる。具体的には
let persistenceController = PersistenceController.shared
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
の2箇所。
Content View
について
- ここからは個別対応になってきて一般化しがたいが、主には下記。
@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
は自分の使いたい変数名にした。
private func addItem() { ... }
private func deleteItems(offsets: IndexSet) { ... }
のところは、呼び方から書き方(Core Data
への保存のしかた)まで、流用した。
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
はプレビューの使いかた。
小ビューに取得したデータベースのレコードをわたす方法が未だにわからない...
データのわたしかたと操作
親ビューでたとえば
NavigationView{
List {
ForEach(records) {record in
RecordRow(record: record)
}
}
と、record
を下層ビューに渡したとする。受け取った下層ビューは
Image(systemName: "checkmark.seal")
.onTapGesture {
record.isDone.toggle()
と書くだけで、タップするトグルされる。
@EnvironmentObject
を使ってrecords
を共有した場合、ModelData.records
はたしかに共有されているが、ビューに渡したrecord
は共有されていない(値渡し)になるので、
ModelData.records[index].isDone.toggle()
などとしないといけない。ところが、@Environment(\.managedObjectContext)
の場合は、参照渡しになっているので、直接書き換えられる。
と理解しているのだけれど違っていたらご指摘ください……
保存のおまけ
新規プロジェクトからの流用ではないが、下記をやっておくとたしかに便利。
ホームボタンを押したときや、アプリを終了したときに、
Core Date
に保存しておいてくれる
Persistence.swift
に
struct PersistenceController {
...
func save() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Show some error here
}
}
}
}
と書いておき、@main
で
@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より。
英語だし、ちょっと説明が飛んでいるところもあるけれど、新しい情報なのですごく助かった。
その他リソース
Add, Delete & Save in Core Data in SwiftUI にもずいぶん助けられた。
カピ通信 - SwiftUI もとてもわかりやすい。残念ながら、発見したのが遅かった。最初に見つけていれば、もっとラクだったのになあ!
おわりに
けっこう悩んだけど、動くとおもしろい!!
ネット上の各種リソースに深謝🙏