Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Swift】Core Dataを使って情報を保存/取得する

 

はじめに

Core Dataを使って、情報を保存する処理と取得する処理について簡単に実装してみたので記事に残します。

 

開発環境

  • macOS Catalina 10.15.7
  • Xcode 12.2
  • Swift 5.3.1 

 

手順

"Use Core Data"にチェックを入れる。

f:id:hfoasi8fje3:20210204225103p:plain

 

Data Modelファイルがプロジェクトに追加される。

f:id:hfoasi8fje3:20210204230019p:plain

 

また、AppDelegate.swiftに永続コンテナ(NSPersistentContainer)を初期化する以下の処理が追加される。

AppDelegate.swift

    // CoreDataを初期化
    // Lazy Stored Propertyによって使用されるまで初期化を遅らせている
    lazy var persistentContainer: NSPersistentContainer = {
        // Data Modelファイル名をイニシャライザに渡してコンテナをインスタンス化する
        let container = NSPersistentContainer(name: "CoreDataSample")
        // StoreCoordinatorを読み込む
        // 存在しない場合はStoreを作成する
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // 書き込み許可されていない場合、Storeにアクセスできない、デバイスの容量不足などの際にエラーになる
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

永続コンテナの初期化が完了すると、コンテナはモデル(NSManagedObjectModel)やコンテキスト(NSManagedObjectContext)、ストアコーディネータ(NSPersistentStoreCoordinator)のインスタンスへの参照を保持する。

※参考:

https://developer.apple.com/documentation/coredata/setting_up_a_core_data_stack

 

ルートビューコントローラー(今回はViewControllerクラス)でCoreDataをインポートし、永続コンテナへの参照を保持する変数を追加。

ViewController.swift

import UIKit
import CoreData

class ViewController: UIViewController {
    
    // 永続コンテナへの参照を保持する変数
    var container: NSPersistentContainer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 永続コンテナのnilチェック
        guard container != nil else {
            fatalError("This view needs a persistent container.")
        }
    }
}

 

Main.storyboardでStoryboard IDを設定。今回はStoryboard IDを"ViewController"とする。

Main.storyboard

f:id:hfoasi8fje3:20210208131108p:plain


SceneDelegate.swiftのscene(_:willConnectTo:options:)で、ルートビューコントローラに永続コンテナに設定する。

※参考:core data - Setting Up CoreData with SceneDelegate - unknown identifier 'window' error - iOS 13 onwards - Stack Overflow

SceneDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let scene = (scene as? UIWindowScene) else {
        return
    }
        
    let window = UIWindow(windowScene: scene)
    self.window = window
let storyboard = UIStoryboard(name: "Main", bundle: nil) guard let rootVC = storyboard.instantiateViewController(identifier: "ViewController") as? ViewController else { return }
// ルートビューコントローラーに永続コンテナを設定する rootVC.container = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer self.window?.rootViewController = rootVC self.window?.makeKeyAndVisible() }

 

エンティティを追加する。

f:id:hfoasi8fje3:20210209230458p:plain

 

 "+"を選択してAttributeを追加する。

f:id:hfoasi8fje3:20210209231210p:plain

 

今回は画像情報を扱うエンティティと仮定して、Attributeを以下のようにしてみる。 

f:id:hfoasi8fje3:20210209231714p:plain

 

Main.storyboardに画像情報を保存するボタンとラベルに保存した情報を表示するためのボタンを追加。

Main.storyboard

f:id:hfoasi8fje3:20210211212436p:plain

 

それぞれの部品をカスタムクラスに関連づける。

f:id:hfoasi8fje3:20210211212453p:plain

 

InsertボタンとFetchボタンにそれぞれ画像の保存処理と取得処理を実装する。ViewController.swiftの全体のコードは以下。

実装後、ビルドしInsertボタンを選択→Fetchボタンを選択すると、ラベルに保存した情報が表示される。

ViewController.swift

import UIKit
import CoreData

class ViewController: UIViewController {
    
    // 永続コンテナへの参照を保持する変数
    var container: NSPersistentContainer!
    
    // 取得した画像情報を保持する配列
    var imageData: [NSManagedObject] = []
    
    // 保存した画像情報を表示するラベル
    @IBOutlet weak var imageIdText: UILabel!
    @IBOutlet weak var imageURLText: UILabel!
    @IBOutlet weak var imageTagText: UILabel!
    @IBOutlet weak var imageLikesText: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 永続コンテナのnilチェック
        guard container != nil else {
            fatalError("This view needs a persistent container.")
        }
    }
    
    // 画像情報を保存
    @IBAction func insertImageData(_ sender: Any) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
        }
        
        let managedContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Image", in: managedContext)!
        let image = NSManagedObject(entity: entity, insertInto: managedContext)
        
        // 保存する画像情報
        image.setValue(1, forKey: "id")
        image.setValue("http://...", forKey: "imageURL")
        image.setValue("sea", forKey: "tag")
        image.setValue(5, forKey: "likes")
        
        appDelegate.saveContext()
    }
    
    // 保存した画像情報を画面に表示
    @IBAction func fetchImageData(_ sender: Any) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
        }
        
        let managedContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Image")
        
        do {
            // 保存した画像情報を取得
            imageData = try managedContext.fetch(fetchRequest)
            
            imageIdText.text = "id:\(String(describing: imageData[0].value(forKey: "id")!))"
            imageURLText.text = "id:\(String(describing: imageData[0].value(forKey: "imageURL")!))"
            imageTagText.text = "id:\(String(describing: imageData[0].value(forKey: "tag")!))"
            imageLikesText.text = "id:\(String(describing: imageData[0].value(forKey: "likes")!))"
        } catch let error as NSError {
            print("Could not fetch. \(error), \(error.userInfo)")
        }
    }
}

 

おわりに

他の記事にもある通り、Appleのドキュメントが古かったり、記事が少なめだったこともあって意外に時間がかかってしまいました・・・。

Core Dataについて、簡単ではありますが試すことができたので、Firebase Cloud Firestoreも機会があれば試してみたいと思います。

※Realm Databaseはだいぶ前に触って既に記事にしていました(忘れてた笑)

【Swift】Realmを触ってみる(レコードの書き込み/取得) - Swift・iOS

 

参考