SwiftUI CoreData 保存の解説 ?

# SwiftUI CoreDataをDocumentsへ保存する話 ?

2019/12/30 修正を更新いたします、データがなしの状態で削除をタップすると飛ぶ挙動を改善しました

2019/12/14 例えば、実機がiPhoneならば? Apple純正アプリの「ファイル」の「このiPhone内」の中に作成したアプリのアイコンが作成されますがinfo.plistに2項目の追加が必要です

2019/12/07

> 目的は? 実機のDocuments内に3つのCoreDataファイルを保存して利用すること?

- 今回のこれらは以下のSwiftUI版です

[Swift5 CoreData 保存の解説](https://qiita.com/papassan/items/09bd978f3d3bb79b9529)

> SwiftUIになったら ? 

- AppDelegate.swiftに「var window: UIWindow?」がない ?
- その代わりSceneDelegate.swiftに「var window: UIWindow?」がある?
- ゆえに以下のコードを変更するとDocumentsへ保存ができる


# 今回のポイントは ?

```SceneDelegate.swift

//変更前
/*⭐️let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
*/

//変更後
/*⭐️*/let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext


```


> 必要な条件は ?

- 新規Productで名前はSwiftUICDSaveToDocでUser interfaceはSwiftUIを選択してUse Core Dataにチェックをして作成します
- 作成後にAppDelegate.swiftとSceneDelegate.swiftとContentView.swiftを差し替えます
- SwiftUICDSaveToDoc.xcdatamodelファイルは変更しています
- SQlite3というディレクトリーは私が勝手に作成したものです
- GeneralのSigningでTeamに入力がNoneでもエラーになりません ?


> SwiftUICDSaveToDoc.xcdatamodelの解説

- ENTITIESをタップして 名前はStudentで1つ作成します
- ENTITIESをタップしてAttributesで+をタップしてAttrivute名をid、TypeをUUIDに設定します
- もう一つ、ENTITIESのAttributesで+をタップしてAttrivute名をname、TypeをStringに設定します
- ENTITIESをタップしてinspectorsをタップしてClassのNameはStudent、ModuleはCurrent Product Module、CodegenはManual/Noneに設定します

> これでDocuments内に「ここまで省略/Documents/SQlite3/SwiftUICDSave.sqlite」のように3種類のファイルが保存されます、それではソースを見てください






# AppDelegate.swift

```AppDelegate.swift

//
//  AppDelegate.swift
//  SwiftUICDSaveToDoc
//
//  Created by 福田 敏一 on 2019/12/07.
//  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 = { print("39-2-persistentContainer・AD・通過")
        /*
         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: "SwiftUICDSaveToDoc")
        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() { print("68-saveContext・AD・通過")
        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("SwiftUICDSave.sqlite")
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 + "/SwiftUICDSave.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: "SwiftUICDSaveToDoc", 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

//
//  SceneDelegate.swift
//  SwiftUICDSaveToDoc
//
//  Created by 福田 敏一 on 2019/12/07.
//  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
*/
/*⭐️*/let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext

        // 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)

        // 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()
    }


}

```

# 2019/12/30 修正を更新いたします、データがなしの状態で削除をタップすると飛ぶ挙動を改善しました

```ContentView.swift

//変更箭

Button(action: {
                    self.moc.delete(self.students[0])
                    
                    try! self.moc.save()
                    
                })



//変更後

                Button(action: {
/*⭐️追加⭐️*/         if self.students.count != 0 {
                    self.contextSaveFile.delete(self.students[0])

                    try! self.contextSaveFile.save()
                    }

                })

```

# ContentView.swift

```ContentView.swift

//
//  ContentView.swift
//  SwiftUICDSaveToDoc
//
//  Created by 福田 敏一 on 2019/12/07.
//  Copyright © 2019 株式会社パパスサン. All rights reserved.
//


import SwiftUI
import CoreData

public class Student: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
}
struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc
    
    @FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>
    
    
    var body: some View {
        VStack {
            List {
                ForEach(self.students, id: \.self) { student in
                    Text(student.name ?? "Unknown")
                }
            }
            
                HStack {
                Button("保存") {
                    let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
                    let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]

                    let chosenFirstName = firstNames.randomElement()!
                    let chosenLastName = lastNames.randomElement()!

                    // more code to come
                    let student = Student(context: self.moc)
                    student.id = UUID()
                    student.name = "\(chosenFirstName) \(chosenLastName)"
                    
                    try! self.moc.save()
                    
                }.padding(.all)
                 .border(Color.blue, width: 5)
                    .foregroundColor(.green)
                    .font(.title)
                    .frame(width: 100, height: 0, alignment: .center)
                    Spacer()
                Button(action: {
/*⭐️追加⭐️*/         if self.students.count != 0 {
                    self.contextSaveFile.delete(self.students[0])

                    try! self.contextSaveFile.save()
                    }

                }) {
                    //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()
            .environment(\.managedObjectContext, (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)
    }
}

```

# 実行結果.swift

```実行結果.swift

17-1・AD・通過
83-4-managedObjectContext・AD・通過
92-5-persistentStoreCoordinator・AD・通過
124-6-managedObjectModel・AD・通過
128-7・AppDelegate・通過・modelURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/5280221E-CBA1-459F-A8B6-9B83C517EACD/data/Containers/Bundle/Application/5F5CE4BF-FA72-4DA6-956C-765809B457A0/SwiftUICDSaveToDoc.app/SwiftUICDSaveToDoc.momd/

132-8-applicationDocumentsDirectory・AD・通過
135-9・AppDelegate・通過・urls by AppDelegate -> [file:///Users/papassan/Library/Developer/CoreSimulator/Devices/5280221E-CBA1-459F-A8B6-9B83C517EACD/data/Containers/Data/Application/4CB49E68-294E-4762-94BC-31416F1D6232/Documents/]

136-10・AppDelegate・通過・urls.count by AppDelegate -> 1

97-11・AppDelegate・通過・url -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/5280221E-CBA1-459F-A8B6-9B83C517EACD/data/Containers/Data/Application/4CB49E68-294E-4762-94BC-31416F1D6232/Documents/SwiftUICDSave.sqlite

111-12・AppDelegate・通過・savePath -> /Users/papassan/Library/Developer/CoreSimulator/Devices/5280221E-CBA1-459F-A8B6-9B83C517EACD/data/Containers/Data/Application/4CB49E68-294E-4762-94BC-31416F1D6232/Documents/SQlite3/SwiftUICDSave.sqlite

114-13・AppDelegate・通過・urlURL -> file:///Users/papassan/Library/Developer/CoreSimulator/Devices/5280221E-CBA1-459F-A8B6-9B83C517EACD/data/Containers/Data/Application/4CB49E68-294E-4762-94BC-31416F1D6232/Documents/SQlite3/SwiftUICDSave.sqlite

88-14・AppDelegate・通過・managedObjectContext -> <NSManagedObjectContext: 0x6000006dc460>



```

# 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は好きですが? いまだにViewのレイアウトとか思うように作成できません? 私は以前はAppleらしくないViewで会計アプリを作成していましたが、これからはSwiftUiを利用すると最もAppleらしいアプリになりそうですね ? 今回の目的がDocuments内に3つのファイルを保存することなのに? 苦労したのはContentView.swiftの実行結果の起動画面View下部の保存とゴミ箱のボタンを両サイドに広げることでした、2つのボタンとかはHStackでくくってからSpacer()でボタンを区切ったらできました、めでたし、めでたしっ

実行の挙動の内容は[ここを参考](https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui)にしました、ありがとうございました


ここまで