はじめに
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 もとてもわかりやすい。残念ながら、発見したのが遅かった。最初に見つけていれば、もっとラクだったのになあ!
おわりに
けっこう悩んだけど、動くとおもしろい!!
ネット上の各種リソースに深謝🙏