SwiftUI2 CoreDataをDocumentsへ保存する話 ?
目的は実機の「ファイル」アプリに保存を表示すること ?
保存して表示する3種類のCore Dataファイル
- SwiftUI2CDSaveToDoc.sql
- SwiftUI2CDSaveToDoc.sql-shm
- SwiftUI2CDSaveToDoc.sql-wal
お急ぎの方は「必要な条件は ?」から入ってコピペしてください
必要条件は ?
- 私のMacはM1 iMac 2021年夏モデル macOS Monterey バージョン 12.0.1
- Xcode Viesion 13.2.1
- 新規プロジェクトはApp選択・iOS選択でNext
- Product Nameは実力者以外は必ずSwiftUI2CDSaveToDocですよ
- Team名は入れて作成しました
- InterfaceはSwiftUI
- LanguageはSwiftでNextで完成です
- 実機の「ファイル」アプリに保存を表示するには ? Info.plistに2つのkeyを追加してください
- iOS Deployment Target iOS15.2
- ContentView.swift以外はコードもSwiftUI2CDSaveToDoc.xcdatamodeldは変更なしです
ContentView.swift
//
// ContentView.swift
// SwiftUI2CDSaveToDoc
//
// Created by Toshikazu Fukuda on 2021/12/20.
// Copyright © 2021 株式会社パパスサン. All rights reserved.
//
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {//🎹🎹🎹ここは私が変更しました
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
.onAppear { self.saveCoreData() }//🔫🔫🔫2021/12/21・ここは私が変更しました これは起動時に呼ばれます 意味不明ですが ?
}
}
}
Text("Select an item")
}
}
func saveCoreData() {//🔫🔫🔫
print("48・通過・func saveCoreData()・by ContentView")
//🎁🎁🎁ここから・2021/12/21・NSPersistentContainerは、iOS10.0以降およびmacOS10.12 +でサポートされています。 以前のターゲットにデプロイする場合は、NSManagedObjectModel、NSPersistentStoreCoordinator、および少なくとも1つのNSManagedObjectContextを手動でインスタンス化する必要があります。参照サイト https://developer.apple.com/documentation/coredata/setting_up_a_core_data_stack/setting_up_a_core_data_stack_manually
//💎💎💎ここから・管理対象オブジェクトモデルを作成する NSManagedObjectModelをインスタンス化するには、コンパイルされたバージョンの.xcdatamodeldファイルを指すURLを渡します。 この.momdファイルは通常、アプリバンドルの一部です。
//ここから・Create a Managed Object Model
guard let modelURL = Bundle.main.url(forResource: "SwiftUI2CDSaveToDoc", withExtension: "momd") else {
fatalError("Failed to find data model")
}
print("⭐️56-1・通過・func saveCoreData()・by ContentView・modelURL -> \(modelURL)\n")
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Failed to create model from file: \(modelURL)")
}
print("⭐️60-2・通過・func saveCoreData()・by ContentView・mom -> \(mom)\n")
//ここまで・Create a Managed Object Model
//ここから・Create a Persistent Store Coordinator
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
print("⭐️65-3・通過・func saveCoreData()・by ContentView・psc -> \(psc)\n")
//ここまで・Create a Persistent Store Coordinator
//💎💎💎ここまで
//🏵🏵🏵ここから・Add a Persistent Store
let dirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
print("⭐️71-4・通過・func saveCoreData()・by ContentView・dirURL -> \(String(describing: dirURL))\n")
// パスを作成
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/SQlite3"
print("⭐️76-5・通過・func saveCoreData()・by ContentView・path -> \(path)\n")
do {
// ディレクトリが存在するかどうかの判定
if !FileManager.default.fileExists(atPath: path) {
// ディレクトリが無い場合ディレクトリを作成する
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false , attributes: nil)
}
} catch {
// エラー処理
}
// 保存先のパスを作成
let savePath = path + "/SwiftUI2CDSave.sqlite"//"/SwiftUI2CDSave.sql"
print("⭐️88-6・通過・func saveCoreData()・by ContentView・savePath -> \(savePath)\n")
// 「パス」を「ファイルURL」に変換
let FileURL = URL(fileURLWithPath: savePath)
print("⭐️91-7・通過・func saveCoreData()・by ContentView・FileURL -> \(FileURL)\n")
let fileURL = URL(string: path + "/SwiftUI2CDSaveToDoc.sql", relativeTo: dirURL)
print("⭐️94-8・fileURL・通過・func saveCoreData() -> \(String(describing: fileURL))\n")
do {
try psc.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: fileURL, options: nil)
} catch {
fatalError("Error configuring persistent store: \(error)")
}
//ここから・Create a Managed Object Context. Create an NSManagedObjectContext, and set its store coordinator property.
let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.persistentStoreCoordinator = psc
//ここまで・Create a Managed Object Context
//🏵🏵🏵ここまで・Add a Persistent Store
//🎁🎁🎁ここまで・2021/12/21・NSPersistentContainerは、iOS10.0以降およびmacOS10.12 +でサポートされています。
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
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)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
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)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
実行結果.text
2021-12-21 13:33:38.615519+0900 SwiftUI2CDSaveToDoc[10613:262295] [error] warning: View context accessed for persistent container SwiftUI2CDSaveToDoc with no stores loaded
CoreData: warning: View context accessed for persistent container SwiftUI2CDSaveToDoc with no stores loaded
48・通過・func saveCoreData()・by ContentView
⭐️56-1・通過・func saveCoreData()・by ContentView・modelURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Bundle/Application/5E179D01-5058-4DE2-93C2-5521B019D46A/SwiftUI2CDSaveToDoc.app/SwiftUI2CDSaveToDoc.momd/
⭐️60-2・通過・func saveCoreData()・by ContentView・mom -> (<NSManagedObjectModel: 0x6000037bfbb0>) isEditable 0, entities {
Item = "(<NSEntityDescription: 0x6000023f94a0>) name Item, managedObjectClassName Item, renamingIdentifier Item, isAbstract 0, superentity name (null), properties {\n timestamp = \"(<NSAttributeDescription: 0x600003ae4580>), name timestamp, isOptional 1, isTransient 0, entity Item, renamingIdentifier timestamp, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 900 , attributeValueClassName NSDate, defaultValue (null), preservesValueInHistoryOnDeletion NO, allowsCloudEncryption NO\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null), uniquenessConstraints (\n)";
}, fetch request templates {
}
⭐️65-3・通過・func saveCoreData()・by ContentView・psc -> <NSPersistentStoreCoordinator: 0x600003fd3480>
⭐️71-4・通過・func saveCoreData()・by ContentView・dirURL -> Optional(file:///Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/)
⭐️76-5・通過・func saveCoreData()・by ContentView・path -> /Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/SQlite3
⭐️88-6・通過・func saveCoreData()・by ContentView・savePath -> /Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/SQlite3/SwiftUI2CDSave.sqlite
⭐️91-7・通過・func saveCoreData()・by ContentView・FileURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/SQlite3/SwiftUI2CDSave.sqlite
⭐️94-8・fileURL・通過・func saveCoreData() -> Optional(/Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/SQlite3/SwiftUI2CDSaveToDoc.sql -- file:///Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/)
AppleのSetting Up a Core Data Stack Manually を参考にしました
実機の「ファイル」アプリに保存を表示する ? Info.plistの設定
Info.plistに下記の2つのkeyを追加してください、詳しくはSupports opening documents in placeとかで検索すると情報を見つけることが可能です
- Supports opening documents in place : YES
- Application supports iTunes file sharing : YES
エラーが出なければMac内に3種類のファイルが保存されます 当然ですが実機ではアプリのファイルの「このiPhone内」で見つけられます
実行結果の⭐️76-5・通過・func saveCoreData()・by ContentView・path -> /Users/papassan/Library/Developer/CoreSimulator/Devices/9B45D0D5-7008-4E41-A0E8-ED395B945FAC/data/Containers/Data/Application/FE304A8A-F062-4D27-80D7-4BB0CADD244B/Documents/SQlite3がMacなどの隠しファイルの中から見つけられます 隠しファイル表示のコマンドはコマンド+シフト+.ですよ
私の感想と意見ですが ?
新しいSwiftUI ? 今回 私はSwiftUI2と命名しましたが ? 今回の条件では自動作成されるファイル構成が大幅に変更になりましたよね ? 思っているのは私だけでしょうか ? そんでもって私の過去の方法は悪戦苦闘しましたがダメでした なら保存できる方法はと考えて起動後にContentView.swift内で書き込みコードを実行しているだけですもんね ? つまりPersistence.swift内で書いたコードだと「warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Item' so +entity is unable to disambiguate.」とか何とか つまり 私は呼び出したつもりはないんですけど何かが1回は呼ばれてるので2回目を呼ばないでよっ まっ、Persistence.swift内だと同じコードで「なーにっ 2回も呼ばないで」って叱られるけど ? ContentView.swift内では文句なしでした 今後はPersistence.swift内で文句言われないように工夫しますもんねって
戸惑い ?
このXcodeのバージョンのSwiftUI2で新規作成したファイルの内訳にInfo.plistがないのに驚いた
ここまで