SwiftUI CoreDataをDocumentsへ保存する話 ? 別の方法
2019/12/15 ご存知でしょうが実機テストする場合はSigning & CapabilitiesのSigningのTeamがNoneだとエラーが出ます
2019/12/14 例えば、実機がiPhoneならば? Apple純正アプリの「ファイル」の「このiPhone内」の中に作成したアプリのアイコンが作成されますがinfo.plistに2項目の追加が必要です
2019/12/12 追加・削除のコードを変えてみましたが? を追加しました
2019/12/11
お急ぎの方は「必要な条件は?」から入ってコピペしてください
前回との違いを? 少し能書きを聞いてください?
前回のSwift5 CoreData 保存の解説 ? 以外の方法を利用しました、以外の方法とは?
- 前回は外部保存はSceneDelegate.swiftからAppDelegate.swiftに書かれたコードを呼びました
- 今回はContentView.swiftに追加したクラスをSceneDelegate.swiftから呼びだしてAppDelegate.swiftに書かれたコードを実行します
私の知識不足が原因ですが? どうしてもこの方法しかありません
今回のポイントは ?
//⭐️変更前⭐️let contentView = ContentView().environment(\.managedObjectContext, context)
//⭐️変更後⭐️
let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(StudentData())
必要な条件は?
- 新規Productで名前は「SwiftUICDSaveToDoc2」でUser interfaceはSwiftUIを選択してUse Core Dataにチェックをして作成します
- 作成後にAppDelegate.swiftとSceneDelegate.swiftとContentView.swiftを差し替えます
- SwiftUICDSaveToDoc.xcdatamodelファイルは変更しています
- SQlite3というディレクトリーは私が勝手に作成したものです
- GeneralのSigningでTeamに入力がNoneでもエラーになりません ?
SwiftUICDSaveToDoc2.xcdatamodelの解説 ⭐️2019/12/12 「SwiftUICDSaveToDoc」は間違えです、修正しました
- ENTITIESをタップして 名前はStudentで1つ作成します
- ENTITIESをタップしてAttributesで+をタップしてAttrivute名をstudent、TypeをStringに設定します
- ENTITIESをタップしてinspectorsをタップしてClassのNameはStudent、ModuleはCurrent Product Module、CodegenはManual/Noneに設定します
これでDocuments内に「ここまで省略/Documents/SQlite3/SwiftUICDSave2.sqlite」のように3種類のファイルが保存されます、それではソースを見てください
AppDelegate.swift
//
// AppDelegate.swift
// SwiftUICDSaveToDoc2
//
// Created by 福田 敏一 on 2019/12/11.
// Copyright © 2019 株式会社パパスサン. All rights reserved.
//
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { print("17-1・AD・通過")
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { print("24-2・AD・通過")
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { print("30・AD・通過")
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
// MARK: - Core Data stack
// ⭐️ 2018/06/06
// アプリ内に保存してある「mamassan.xcdatamodeld」を読込む為の初期化作業です
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "SwiftUICDSaveToDoc2")
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)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.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)")
}
}
}
// ⭐️ 2019/5/3・ここから・保存に必要なコードです
lazy var managedObjectContext: NSManagedObjectContext = { print("83-4-managedObjectContext・AD・通過")
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
print("88-14・AppDelegate・通過・managedObjectContext -> \(managedObjectContext)\n")
return managedObjectContext
}()
// ⭐️ 2019/5/3・iOS 10以前のクラス
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { print("92-5-persistentStoreCoordinator・AD・通過")
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = applicationDocumentsDirectory.appendingPathComponent("SwiftUICDSaveToDoc2")
print("97-11・AppDelegate・通過・url -> \(url!)\n")//urlはディレクトリーなし
// パスを作成
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/SQlite3"
do {
// ディレクトリが存在するかどうかの判定
if !FileManager.default.fileExists(atPath: path) {
// ディレクトリが無い場合ディレクトリを作成する
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false , attributes: nil)
}
} catch {
// エラー処理
}
// 保存先のパスを作成
let savePath = path + "/SwiftUICDSave2.sqlite"
print("111-12・AppDelegate・通過・savePath -> \(savePath)\n")
// 「パス」を「ファイルURL」に変換
let urlURL = URL(fileURLWithPath: savePath)
print("114-13・AppDelegate・通過・urlURL -> \(urlURL)\n")
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: urlURL, options: nil)
} catch {
// エラー処理
}
return coordinator
}()
// ⭐️ 2019/5/3・iOS 10以前のクラス
lazy var managedObjectModel: NSManagedObjectModel = { print("124-6-managedObjectModel・AD・通過")
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
// 2019/05/03・.mondとは ? momdファイルは実際にはすべてのバージョンモデルを含むコンテナ
let modelURL = Bundle.main.url(forResource: "SwiftUICDSaveToDoc2", withExtension: "momd")!
print("128-7・AppDelegate・通過・modelURL -> \(modelURL)\n")
return NSManagedObjectModel(contentsOf: modelURL)!
}()
// ⭐️ ファイルを保存するために使用するディレクトリ
lazy var applicationDocumentsDirectory: NSURL = { print("132-8-applicationDocumentsDirectory・AD・通過")
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.test.ConfigurationTest" in the application's documents Application Support directory.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
print("135-9・AppDelegate・通過・urls by AppDelegate -> \(urls)\n")
print("136-10・AppDelegate・通過・urls.count by AppDelegate -> \(urls.count)\n")
return urls[urls.count-1] as NSURL
}()
}
SceneDelegate.swift
//
// SceneDelegate.swift
// SwiftUICDSaveToDoc2
//
// Created by 福田 敏一 on 2019/12/11.
// Copyright © 2019 株式会社パパスサン. All rights reserved.
//
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Get the managed object context from the shared persistent container.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
// Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
// Add `@Environment(\.managedObjectContext)` in the views that will need the context.
//⭐️変更前⭐️let contentView = ContentView().environment(\.managedObjectContext, context)
//⭐️変更後⭐️
let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(StudentData())
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
// Save changes in the application's managed object context when the application transitions to the background.
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
}
ContentView.swift
//
// ContentView.swift
// SwiftUICDSaveToDoc2
//
// Created by 福田 敏一 on 2019/12/11.
// Copyright © 2019 株式会社パパスサン. All rights reserved.
//
import SwiftUI
import Combine
import CoreData
var students = [String]()
var isSave = false
final class StudentData: ObservableObject {
let appDelegate: AppDelegate
let moc: NSManagedObjectContext
init() {
print("23-3 Counter data initialized・CV")
appDelegate = UIApplication.shared.delegate as! AppDelegate
moc = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
let results = try! moc.fetch(fetchRequest) as! [Student]
//起動時に通過します、初期では配列は空なのいで結果は[]です
print("32-15通過・students・cv -> \(students)")
if results.capacity > 0 {
for i in 0..<results.count {
students += ["\(results[i].student)"]
}
//1つ以上の保存データがあれば通過します
print("38-16通過・students -> \(students)")
}
}
let didChange = PassthroughSubject<StudentData, Never>()
@Published var name: String = "" {
didSet {
print("Data changed to", self.name)
didChange.send(self)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
do {
if self.name == "" && students != [] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
var daleteStudents: [Student] = []
daleteStudents = try! moc.fetch(fetchRequest) as! [Student]
moc.delete(daleteStudents.last!)
students.removeLast()
try moc.save()
return
}
let results = try moc.fetch(fetchRequest) as! [Student]
if results.capacity > 0 {
print("Updating existing entity", self.name)
let Updating = NSEntityDescription.insertNewObject(forEntityName: "Student", into: moc) as! Student
Updating.student = self.name
} else {
print("Creating new entity", self.name)
let newEntity = NSEntityDescription.insertNewObject(forEntityName: "Student", into: moc) as! Student
newEntity.student = self.name
students = ["\(self.name)"]
}
if isSave == true {
print("70・通過・context.save()")
try moc.save()
isSave = false
}
} catch {
// Do something in response to error condition
print("Failed trying to save")
print("Error info: \(error)")
}
do {
students = []
let results = try moc.fetch(fetchRequest) as! [Student]
for i in 0..<results.count {
students += ["\(results[i].student)"]
print("2通過・students -> \(students)")
}
} catch {
// Do something in response to error condition
print("Failed trying to save")
print("Error info: \(error)")
}
}
}
}
public class Student: NSManagedObject {
@NSManaged public var student: String
}
struct ContentView : View {
@EnvironmentObject var counterData: StudentData
@State var hasClicked: Bool = false
private func saveCoreData(chosenFirstName: String, chosenLastName: String) {
print("保存")
isSave = true
self.counterData.name = "\(chosenFirstName) \(chosenLastName)"
}
private func deleteCoreData() {
print("削除")
self.hasClicked = true
self.counterData.name = ""
}
var body: some View {
VStack {
List {
ForEach(students, id: \.self) { student in
Text(student)
}
}
HStack {
Button("保存") {
let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]
let chosenFirstName = firstNames.randomElement()!
let chosenLastName = lastNames.randomElement()!
self.saveCoreData(chosenFirstName: chosenFirstName, chosenLastName: chosenLastName)
}.padding(.all)
.border(Color.blue, width: 5)
.foregroundColor(.green)
.font(.title)
.frame(width: 100, height: 0, alignment: .center)
Spacer()
Button(action: {
self.deleteCoreData()
}) {
//Text("削除")
Image(systemName: "trash")
}.padding(.all)
.background(Color.red)
.cornerRadius(40)
.foregroundColor(.black)
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.purple, lineWidth: 5))
}.padding(20)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(StudentData())
}
}
実行結果.swift
17-1・AD・通過
24-2・AD・通過
23-3 Counter data initialized・CV
83-4-managedObjectContext・AD・通過
92-5-persistentStoreCoordinator・AD・通過
124-6-managedObjectModel・AD・通過
128-7・AppDelegate・通過・modelURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/CD6AD351-207C-4E39-9B2D-88B69E03885D/data/Containers/Bundle/Application/5C9F1E9F-35F7-4A19-8771-B3ABAC812633/SwiftUICDSaveToDoc2.app/SwiftUICDSaveToDoc2.momd/
132-8-applicationDocumentsDirectory・AD・通過
135-9・AppDelegate・通過・urls by AppDelegate -> [file:///Users/papassan/Library/Developer/CoreSimulator/Devices/CD6AD351-207C-4E39-9B2D-88B69E03885D/data/Containers/Data/Application/419E803B-6BE3-4E5F-9095-DE3B98EE0537/Documents/]
136-10・AppDelegate・通過・urls.count by AppDelegate -> 1
97-11・AppDelegate・通過・url -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/CD6AD351-207C-4E39-9B2D-88B69E03885D/data/Containers/Data/Application/419E803B-6BE3-4E5F-9095-DE3B98EE0537/Documents/SwiftUICDSaveToDoc2
111-12・AppDelegate・通過・savePath -> /Users/papassan/Library/Developer/CoreSimulator/Devices/CD6AD351-207C-4E39-9B2D-88B69E03885D/data/Containers/Data/Application/419E803B-6BE3-4E5F-9095-DE3B98EE0537/Documents/SQlite3/SwiftUICDSave2.sqlite
114-13・AppDelegate・通過・urlURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/CD6AD351-207C-4E39-9B2D-88B69E03885D/data/Containers/Data/Application/419E803B-6BE3-4E5F-9095-DE3B98EE0537/Documents/SQlite3/SwiftUICDSave2.sqlite
88-14・AppDelegate・通過・managedObjectContext -> <NSManagedObjectContext: 0x6000028a0540>
32-15通過・students・cv -> []
2019/12/1 追加・削除のコードを変えてみましたが?
//⭐️Listの配列のみ表示変更する場合⭐️//
if self.name == "" && students != [] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
var daleteStudents: [Student] = []
daleteStudents = try! moc.fetch(fetchRequest) as! [Student]
moc.delete(daleteStudents.last!)
students.removeLast()
try moc.save()
return
}
//⭐️CoreDataから配列に読み込む場合⭐️//
if self.name == "" && students != [] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
var daleteStudents: [Student] = []
daleteStudents = try! moc.fetch(fetchRequest) as! [Student]
moc.delete(daleteStudents.last!)
//students.removeLast()
try moc.save()
students = []
let results = try moc.fetch(fetchRequest) as! [Student]
for i in 0..<results.count {
students += ["\(results[i].student)"]
print("2通過・students -> \(students)")
}
return
}
2019/12/14 実機の「ファイル」アプリに保存を表示する ?
Info.plistに下記の2つのkeyを追加してください、詳しくはSupports opening documents in placeとかで検索すると情報を見つけることが可能です
- Supports opening documents in place : YES
- Application supports iTunes file sharing : YES
私の感想と意見ですが ?
- 追加・削除のコードを変更してみましたが挙動は変わりないみたい?
私はSwiftUIは少ししか理解していませんが、私は少しでも新しいAppleの方針を理解する必要があると思っています、で、少しコードの解説です? 保存のコードはListの配列表示に関係なくCoreDataの読み込みで表示していますが? 削除はListの配列を削除して表示しています、なんか? 挙動が遅いでしょう? これは保存と同様にListの配列をCoreDataの読み込み後のListの配列の表現にすればよかったのですが? 最初はListの配列とCoreDataの配列の? 配列の意味が私が無知だったのでした? 今でも無知ですが、私は仕事と勉強と努力は死ぬまで頑張ります?
実行の挙動の内容は前回同様のここを参考とクラスの追加で従来のようにコードが書ける? とうの方法はここを参考にしました、ありがとうございました
ここまで