[iOSアプリ開発] Todoリスト表示アプリ:CoreData利用

CoreDataを利用したTodoリストアプリを作成してみませんか?

データ永続化をするために、Userdefaultsからスタートし、NSCoderを経て、最終的にCoreDataを選択していますので、この記事は、データベースの違いを確認することもできます。

目次

概要

この講座で学習する内容
  • Todoリスト表示アプリ作成

アプリを作成する過程で、以下の内容を学習できます。

  • TableViewControllerの概要
  • UserDefaultsを利用したデータの永続化
  • NSCoderを利用したデータの永続化
  • CoreDataを利用したデータの永続化

続編→Realmを利用したデータの永続化もあります。

MVCデザインパターンを適用しています。
Model(M)、View(V)、Controller(C
記事中、M、V、Cの略を使用しています。

参考にした講座】iOS & Swift – The Complete iOS App Development Bootcamp (Udemy)
 (Section19) Local Data Persistance – User Defaults, Core Data and Realm

この講座で作成するアプリの概要
  1. このアプリのデータはCoreDataを利用して永続化をしていますので、アプリを終了、クラッシュしてもデータが消えることはありません。
  2. カテゴリ画面は、カテゴリーのリストが表示されています。右上のプラスボタンでカテゴリーを追加することができます。
  3. あるカテゴリーを選択すると、次画面(Todoリスト画面)に遷移します。
  4. Todoリスト画面は、選択したカテゴリーのリストを表示します。
  5. 右上のプラスボタンでリストを追加することができます。
  6. 上部にあるSearchBarを使用して特定のリストを抽出することができます。
アプリの開発環境(2023年5月5日現在)
  • Xcode: Version 14.3.1
  • macOS: Venture: Version 13.4.1
  • iOS: 16.5.1
Xcodeのインストールが未了の方はこちら
GitHubからプロジェクトを入手したい方はこちら

UIの設定

開始プロジェクトをクローンして起動した状態を確認してみましょう。

開始プロジェクトの確認

開始プロジェクトの確認

Main.storyboardファイル→初期状態
ViewControllerファイル→初期状態

TableViewControllerの使用

TableViewControllerの概要
  • TableViewControllerは、UIViewControllerを継承して作られた特殊なビューコントローラです。
  • UIViewControllerはiOSアプリ開発において一般的に使用されるビューコントローラであり、画面の表示や操作に関する処理を担当します。
  • TableViewControllerUITableViewDelegateUITableViewDataSourceプロトコルを実装することができ、TableViewの表示や操作に関する処理を簡単に実装することができます。

TableViewController(ビュー)の設定

STEP
TableViewControllerの選択

オブジェクトライブラリからTableViewControllerを選択し、Main.storyboardにドラッグします。

STEP
ViewControllerの削除

初期ビューコントローラを示す矢印をTableViewControllerに移動後、ViewControllerを削除します。

STEP
ViewControllerの名称を変更する
  1. 名称変更:UIViewControllerUITableViewController
  2. 名称変更:ViewControllerTodoListViewController
STEP
TableViewControllerをコード(TodoListViewController)とリンクする
STEP
黄色のエラー「Prototype table…」を解消
  1. TableViewCellを選択する
  2. Identifierに「ToDoItemCell」と入力する

ナビゲーションバーの設定

STEP
ナビゲーションバーを表示する

「Editor」→「Embed In」→「Navigation Controller」を選択する。

STEP
ナビゲーションバーのタイトルを設定する

タイトル「Todoey」にする。

データをTableViewに表示

STEP
配列のデータをTableViewに表示する
STEP
TableViewの選択したアイテムの情報を出力する

以下のコードを追記します。
選択したセルがグレーに点滅後、白色に戻す設定のコードも追記しています。

//MARK - TableView Delegate Methods
    
override func tableView(_ tableView: UITableView, 
    didSelectRowAt indexPath: IndexPath) {
    print(itemArray[indexPath.row])
        
//選択したセルがグレーに点滅後、白色に戻す設定
    tableView.deselectRow(at: indexPath, 
        animated: true)
}
STEP
TableViewの選択したアイテムにチェックマークを付ける

以下のコードを追記します。

チェックマークが既に付いている場合は、チェックマークを外すようにしています。

if tableView.cellForRow(at: indexPath)?
    .accessoryType == .checkmark {
    tableView.cellForRow(at: indexPath)?
        .accessoryType = .none
} else {
    tableView.cellForRow(at: indexPath)?
        .accessoryType = .checkmark
}

アイテム追加ボタンの設定

STEP
ナビゲーションバーにアイテム追加ボタンを作成

オブジェクトライブラリから、「Bar Button Item」をナビゲーションバーに追加します。

STEP
Bar Button Itemのスタイルの設定
  • System Itemを「Add」にする。→見た目が「+」に変わる。
  • Tintを「White Color」にする。
STEP
Addボタンとコードのリンク

Addボタンとコードをリンクする。名前は「addButtonPressed」

STEP
Addボタンを押した時に、「Success」と出力するコードの作成
import UIKit

class TodoListViewController: UITableViewController {
    
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {

        let alert = UIAlertController(
            title: "Add New Todoey Item", 
            message: "", 
            preferredStyle: .alert)

        let action = UIAlertAction(
            title: "Add Item", 
            style: 
            .default) { (action) in

 //Addボタンが押された時の処理
            print("Success")
        }

 // UIAlertActionをUIAlertControllerに追加
        alert.addAction(action)

 // UIAlertControllerを表示
        present(alert, animated: true, completion: nil)
    }
}
  • まず、UIAlertControllerのインスタンスalertを作成します。
    titleパラメータ:アラートのタイトル
    messageパラメータ:アラートのメッセージ
    .alertスタイルを指定します。
  • 次に、UIAlertActionのインスタンスactionを作成します。
    titleパラメータ:「Add Item」というタイトルを持つアクションを作成
    styleパラメータ:アクションのスタイル
    .defaultスタイルは通常のボタンスタイルを意味します。
  • さらに、クロージャ内にはボタンが押された時の処理を記述します。上記のコードでは、ボタンが押された時に「Success」というメッセージをコンソールに表示しています。
  • addAction()メソッドを使用して、作成したUIAlertActionをUIAlertControllerに追加します。このようにして複数のアクションをアラートに含めることができます。
  • 最後に、present()メソッドを使用してUIAlertControllerを表示します。
    animatedパラメータをtrueに設定することで、アラートの表示にアニメーションが適用されます。
STEP
Addボタンを押したとき、PlaceFolderに入力した内容をテーブルに表示するコードの作成
import UIKit

class TodoListViewController: UITableViewController {

    var itemArray = ["Find Mike", 
                     "Buy Eggos", 
                     "Destory Demogorgon"]
    
//Addボタンが押された時の処理
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
            self.itemArray.append(textField.text!)
            self.tableView.reloadData()
     }

// UIAlertActionをUIAlertControllerに追加
        alert.addTextField { (alertTextField) in
            alertTextField.placeholder = "Create new item"
            textField = alertTextField
        }
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }
}
  • alert.addTextField { (alertTextField) in ... }という形式でテキストフィールドを追加します。
  • クロージャ内では、alertTextFieldという引数名で追加されたテキストフィールドを参照できます。
  • ここでは、alertTextField.placeholderを使用してテキストフィールドにプレースホルダーテキストを設定しています。
  • また、alertTextField.textを使用してテキストフィールドに入力されたテキストを取得し、変数textFieldに代入しています。
  • Addボタンを押すと、textFieldの内容を配列itemArrayに追加します。
  • tableView.reloadData()を実行し、追加した内容がTableViewに表示されます。

データの永続化(UserDefaults)について

今、スマホのホームボタンを押して、アプリがバックグラウンドになっても、そのデータはスマホのメモリーに保存されたままです。→まだ、データは存在している!

しかし、以下のような時、アプリがバックグラウンドに入るのではなく、終了してしまった場合は、データは消えてしまいます。→データが消える!

データが消える場合の例
  • アプリが強制終了する場合
  • アプリのアップデートでインストールした場合
  • ユーザがOSをアップデートした場合

そこで、小さなデータを永続化する最も簡単な方法の一つである、「Userdefaults」を使用していきます。

UserDefaultsの使用

UserDefaultsを使用したコードを以下のように追加しました。

UserDefaults

1.UserDefaultsのインスタンスを取得します

2.Addボタンを押した時、追加したデータを、キー「TodoListArray」を指定して、Userdefaultsに保存します。

3.アプリ起動後、UserDefaultsのデータをitemArrayに読み込みます。

アプリを終了後、再度アプリを起動しましたが、最後に追加したデータ「UserDefaultsに追加」は、UserDefaultsに保存されているため、消されずに表示することができました。

格納データを配列からクラスに変更

再利用セルを使用する場合、以下のような問題があります。

UITableViewはセルの再利用を行うため、スクロールなどでセルが画面外に移動すると再利用セルとしてプールされます。再利用セルはそのまま次のセルとして表示されるため、以前にチェックマークがついていた場合でも再利用セルにはチェックマークが残っている可能性があります。

このため、セルではなく、データにチェックマークの情報(プロパティ)を持たせるため、データを配列からクラスに格納するように変更し、合わせてMVCデザインパターンを導入します。

MVCデザインパターンの導入
  • 上から、Controllersフォルダ、Modelフォルダ、Viewフォルダを作成し、対応するファイルを格納します。
  • Modelフォルダには、itemファイルを新規作成したものを格納しています。
データを配列からクラスに格納するように変更+チェックマーク情報のプロパティ追加
import Foundation

class Item {
    var title: String = ""
    var done: Bool = false
}
  • クラスItemのインスタンスitemArrayを作成する
  • 配列にあったデータを、itemArrayに格納する
  • itemArrayのプロパティtitleを呼び出す
  • 新アイテムをitemArrayに格納する

チェックマークの情報をコード追記

チェックマークの情報をコード追記
  1. コードを見やすくするため(文字の置き換え)
  2. クラスのプロパティdoneの内容から、チェックマークの付与を判断
  3. クリックするたびに、チェックマークが有→無→有と変わるようにする

ここで、上記②のif-else文を1行にコードを短縮します。

上記②のif-else文のコード短縮

変更前

if item.done == true {
    cell.accessoryType = .checkmark
} else {
    cell.accessoryType = .none
}

変更後

cell.accessoryType = 
    item.done ? .checkmark : .none

UserDefaultsの使用上の問題

UserDefaultsのデータ読み込みコードの変更

UserDefaultsのデータ読み込みコードを変更します。
データを取り出す形式を[String] → [Item]に変更しています。

変更前

// UserDefaultsのデータをitemArrayに読み込み
if let items = defaults.array(forKey: "TodoListArray") 
    as? [String] {
    itemArray = items
}

変更後

// UserDefaultsのデータをitemArrayに読み込み
if let items = defaults.array(forKey: "TodoListArray") 
    as? [Item] {
    itemArray = items
}

しかし、ここでアプリを実行すると問題(エラー)が生じます。

エラー
[User Defaults] Attempt to set a non-property-list object (
    "Todoey.Item",
    "Todoey.Item",
    "Todoey.Item",
    "Todoey.Item"
) as an NSUserDefaults/CFPreferences value for key TodoListArray

このエラーは、UserDefaultsを使用して非プロパティリストオブジェクトを設定しようとしたためです。
UserDefaultsはプロパティリスト(キーバリューペア)である必要があります。

しかし、今回のケースは、カスタムItemオブジェクトの配列を保存しようとしたためです。

そこで、別のデータ永続化の方法を検討することにします。

データの永続化(NSCoder)の使用

前章においては、非プロパティリストオブジェクトをNSUserDefaultsに保存しようとした際にエラーが生じました。

このエラーを解消するために、NSCoderを使用してカスタムオブジェクトをエンコードおよびデコードする方法に変更します。以下、その方法について、コードを見ながら解説していきます。

エンコード、デコード

エンコードデコードは、データの形式を変換するプロセスを指します。データをエンコードすることは、そのデータを別の形式に変換して表現することであり、デコードはその逆の操作です。

具体的には、データをエンコードすることで、メモリ上のオブジェクトやデータ構造をバイト列やテキストなどの形式に変換します。これにより、データをファイルに保存したり、ネットワーク経由で送信したりすることが可能になります。

一方、デコードはエンコードされたデータを元の形式に戻すプロセスです。デーコードによって、バイト列やテキスト形式のデータを元のオブジェクトやデータ構造に変換し、それを利用することができます。

plistファイルのパスを作成

//独自のplistファイルのパスを作成
let dataFilePath = FileManager
    .default
    .urls(for: .documentDirectory, in: .userDomainMask)
    .first?
    .appendingPathComponent("Items.plist")

このコードは、ファイルマネージャを使用してアプリのドキュメントディレクトリ内に特定のファイルパスを作成しています。

  • FileManager.default:
    デフォルトのファイルマネージャのインスタンスを取得します。
  • urls(for:in:)メソッド:
    アプリのドキュメントディレクトリのURLを取得します。
    このコードでは、.documentDirectoryを指定しているため、アプリのドキュメントディレクトリのURLが返されます。
    .userDomainMaskは、ユーザーのホームディレクトリ内の検索を示す定数です。
  • .first:
    配列の最初の要素、つまりアプリのドキュメントディレクトリのURLを取得します。
  • appendingPathComponent(_:)メソッド:
    アプリのドキュメントディレクトリのURLに特定のファイル名(”Items.plist“)を追加します。
    このメソッドは、指定した文字列を元のURLに追加し、新しいURLを作成します。
  • 取得したファイルパスをdataFilePathという定数に代入しています。

ItemクラスをCodableプロトコルに準拠

class Item: Codable {
    var title: String = ""
    var done: Bool = false
}

このクラスはCodableプロトコルに準拠しており、エンコードおよびデコード可能なオブジェクトであることを示しています。

Codableプロトコル

Codableプロトコルは、Swiftの標準ライブラリで提供されているプロトコルの一つです。このプロトコルを採用することで、データのエンコード(シリアライズ)およびデコード(デシリアライズ)が簡単に行えるようになります。

Codableプロトコルを採用することで、オブジェクトを構造化されたデータ形式(例:JSON、プロパティリスト)に変換し、その逆の操作も行えます。具体的には、Codableプロトコルを採用したクラスや構造体のプロパティが自動的にエンコードおよびデコード可能になります。

エンコード:itemArrayデータ→dataFilePath

class TodoListViewController: UITableViewController {
    
//MARK - TableView Delegate Methods  
    override func tableView(_ tableView: UITableView, 
    didSelectRowAt indexPath: IndexPath) {
           
        saveItems()
    }
    
//MARK -Add New Items  
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
 
        saveItems()
    }
    
    func saveItems() {
        //itemArrayデータをエンコードして、dataFilePathに保存
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(itemArray)
            try data.write(to: dataFilePath!)
        } catch {
            print("Error encoding item array, \(error)")
        }
        tableView.reloadData()
    }
}
  • データを保存するためには、saveItems関数が使用されます。
  • 保存するデータをエンコードするためには、PropertyListEncoderのインスタンスを作成します。
  • PropertyListEncoderは、プロパティリスト形式にデータをエンコードするためのクラスです。
    エンコードするデータはitemArrayです。

エンコードされたデータをファイルに保存するために、do-catch文を使用します。

  • まず、encoder.encodeメソッドを使用してitemArrayをエンコードし、エンコードされたデータをdata変数に代入します。
  • その後、data.writeメソッドを使用してデータをdataFilePathに書き込みます。
  • エンコードやファイル書き込み中にエラーが発生した場合は、catch節でエラーメッセージを表示します。

デコード:dataFilePath→itemArrayデータ

class TodoListViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        loadItems()
    }
    
    func loadItems() {
        //dataFilePathに保存されているデータをデコードしてitemArrayに格納
        if let data = try? Data(contentsOf: dataFilePath!) {
            let decoder = PropertyListDecoder()
            do {
                itemArray = try decoder.decode([Item].self, from: data)
            } catch {
                print("Error decoding item array, \(error)")
            }
        }
    }
}
  • データを読み込むためには、loadItems関数が使用されます。
  • dataFilePathに保存されているデータを読み込むために、Data(contentsOf:)メソッドを使用し、データをdata変数に代入します。
  • データのデコードには、PropertyListDecoderのインスタンスを作成します。
  • decoder.decodeメソッドを使用して、data[Item].self(Itemオブジェクトの配列)にデコードし、その結果をitemArrayに代入します。
  • デコード中にエラーが発生した場合は、catch節でエラーメッセージを表示します。

以上のコードを通じて、カスタム型のデータをエンコードおよびデコードし、NSUserDefaultsに保存および読み込む方法が実装されました。

データの永続化(CoreData)の使用

次は、CoreDataを使用したデータ永続化の方法を考えていきます。

CoreDataの設定

新規プロジェクト作成(CoreData)

  • 新規プロジェクトを作成します
  • 「iOS」→「App」→「Next」をクリックします。
  • Project Nameなど登録します。ここで、「Use Core Data」にチェックをすることがポイントです。

テンプレートが開くと、通常とは違う点が2つあります。

  1. AppDelegateの中に、Core Dataを使うために必要なコードが追加されている。
  2. 「CoreDataTest.xcdatamodeled」が追加されている。
AppDelegateの中に、Core Dataを使うために必要なコードが追加されている。
→後で使用します

既存アプリにCoreDataを追加する方法

アプリを作成するとき、最初からCoreDataを使用することを決めていた場合は、「Use Core Data」にチェックを入れて進めていけば問題ありません。

しかし、途中でCoreDataを追加したくなる場合もありますよね。これからはその方法について解説します。

STEP
CoreDataモデルを追加します

「File」→「New」→「File」を選択します

次に、「Data Model」→「Next」を選択します

データモデル名を「DataModel」とし、「Create」をクリックします

STEP
AppDelegateにCoreDataを使うために必要なコードの追加

Use Core Data」にチェックを入れて新規プロジェクトを作成したときに作成される、AppDelegateの中にある、CoreDataを使うために必要なコードをコピーします。

  1. AppDelegateにコピーする
  2. self.saveContext()」を追記する
  3. import CoreData」を分頭に追記する
// MARK: – Core Data stack
  • persistentContainerという変数が定義されています。
    アプリのデータモデルを含むNSPersistentContainerオブジェクトを遅延評価(lazyで生成するためのコードです。
  • persistentContainerNSPersistentContainer(name: "DataModel")を使用して初期化されます。
    この初期化では、”DataModel“という名前のデータモデルを指定してNSPersistentContainerオブジェクトを作成します。データモデル名は自身のプロジェクトで使用しているデータモデルの名前に合わせて変更する必要があります
  1. container.loadPersistentStores(completionHandler: { (storeDescription, error) in ...という部分があります。
  2. ここでは、作成したNSPersistentContainerオブジェクトに対して永続ストア(データがディスク上に永続的に保存される場所)の読み込みを行います。
  3. この処理は非同期で行われ、読み込みの完了後に渡されるクロージャ(completionHandler)内でエラーハンドリングが行われます。
  4. もしエラーが発生した場合は、fatalError("Unresolved error \(error), \(error.userInfo)")が呼び出されます。
  5. このエラーは致命的なエラーとして扱われ、アプリの実行が停止します。エラーメッセージには具体的なエラーの詳細情報が含まれます。
// MARK: – Core Data Saving support
  • データの変更を保存するためのsaveContextメソッドが定義されています。
  • saveContextメソッドでは、まずpersistentContainer.viewContextを使用して現在のコンテキスト(Context)を取得します。
  • 次に、context.hasChangesを使用してコンテキスト内の変更があるかどうかを確認します。
  • 変更がある場合は、context.save()を呼び出して変更を保存します。
  • もし保存中にエラーが発生した場合は、fatalError("Unresolved error \(nserror), \(nserror.userInfo)")が呼び出され、アプリの実行が停止します。ここでもエラーメッセージには具体的なエラーの詳細情報が含まれます。

これで、「Use Core Data」にチェックを入れて新規プロジェクトを作成したときと同じ状態になりました。

ItemクラスをItemエンティティへ置き換え

Itemクラス(オブジェクト指向プログラミング)とItemエンティティ(Core Data)は以下のように対応します。

Databaseオブジェクト指向プログラミング
Itemクラス
Core Data
Itemエンティティ
TableClassEntity
FieldプロパティAttributes
Itemクラス
Itemエンティティ

Itemエンティティが置き換えられたことを確認し、Itemクラスのファイル「item.swift」を削除します。

エンティティの登録画面で使用されるオプション

次に、エンティティ(Entity)の登録画面で使用されるオプションについて解説します。今回のアプリにおいては、「Module」は、Current Product Module、「Codegen」は、Class Definitionを選択します。

Module

Module」は、データモデルが所属するモジュールを指定するためのオプションです。

  • プロジェクト内の特定のグループやフォルダを示し、コードの整理や役割分担を容易にするために使用されます。
  • 通常、モジュール名はプロジェクト名やターゲット名と関連付けられます。
  • エンティティを特定のモジュールに関連付けることで、エンティティを利用する際には、適切なモジュールをインポートする必要があります。
Codegen

Codegen」は、エンティティに関連するクラスの生成方法を指定するオプションです。
CoreDataは、エンティティに基づいてデータモデルクラスを自動的に生成しますが、「Codegen」オプションによってその方法を制御できます。

「Class Definition」:
オプションを選択すると、エンティティに対応するクラスの定義が自動生成されます。このクラスは、エンティティの属性や関係をプロパティとして持ち、データの操作を行うためのメソッドを提供します。このオプションを選択すると、開発者が生成されたクラスを拡張してカスタマイズすることができます。

「Category/Extension」:
オプションを選択すると、エンティティに対応するカテゴリ(Category)または拡張(Extension)が自動生成されます。このオプションでは、既存のクラスに対して追加の機能やプロパティを提供するためのメソッドやプロパティを定義できます。

「None」:
オプションを選択すると、自動生成されるクラスや拡張は行われず、開発者が独自のクラスを作成する必要があります。このオプションを選択する場合、エンティティに対応するクラスを自分で作成し、必要な属性やメソッドを手動で定義する必要があります。

CoreData:保存・読み込み・削除

CoreData:データ保存

CoreDataを使用したデータを保存する部分のコード(抜粋)を紹介します。

import UIKit
import CoreData

class TodoListViewController: UITableViewController {

//解説1:    
    let context = (UIApplication.shared.delegate as! AppDelegate)
        .persistentContainer.viewContext
   
//MARK -Add New Items   
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {      
        var textField = UITextField()
        
        let alert = UIAlertController(
            title: "Add New Todoey Item", 
            message: "", preferredStyle: .alert)
        let action = UIAlertAction(
            title: "Add Item", style: .default) { (action) in
            //Addボタンが押された時の処理
//解説2:
            let newItem = Item(context: self.context)
            newItem.title = textField.text!
            newItem.done = false
            self.itemArray.append(newItem)

            self.saveItems()
    }
    
    func saveItems() {
        do {
//解説3:
            try context.save()
        } catch {
            print("Error saving context \(error)")
        }
        tableView.reloadData()
    }
}
解説1:let context …
let context = 
    (UIApplication.shared.delegate as! AppDelegate)
    .persistentContainer
    .viewContext
  • (UIApplication.shared.delegate as! AppDelegate)
    (AppDelegate)にアクセスするためのコードです。AppDelegateはアプリケーションのライフサイクルイベントを管理するクラスであり、Core Dataのスタック(データの読み込みや保存を管理する仕組み)もここで定義されています。
  • .persistentContainer
    AppDelegate内で定義されたプロパティであり、Core Dataのデータモデルやデータベースとのやり取りを担当するNSPersistentContainerオブジェクトを返します。
  • .viewContext
    NSPersistentContainerオブジェクトから現在のContextを取得します。

コンテキストは、データベースとのデータ操作を行うためのインターフェースです。このコンテキストを使用して、データの追加、変更、削除などの操作を行うことができます。

まとめると、この行のコードは、UIApplication.shared.delegateを使用してAppDelegateにアクセスし、そこから取得したNSPersistentContainerオブジェクトのviewContextプロパティを使用してCore DataのContextを取得しています。これにより、Contextを介してデータベースとやり取りし、データの永続化や操作を行うことができます。

解説2:let newItem …
let newItem = 
    Item(context: self.context)
  • Item(context: self.context):
    Itemというエンティティのインスタンスを作成しています。
  • このインスタンスを作成する際に、引数としてself.contextが指定されています。
  • self.contextは、先ほど取得したCore DataのContextです。
  • Contextは、データベースとのデータ操作を行うためのインターフェースです。ここでは、Contextを使用して新しい項目を作成しています。

新しい項目の作成には、特定のデータモデル(エンティティ)に基づいて行われます。そのため、Itemというエンティティの定義がCore Dataのデータモデル内に存在する必要があります。

  1. このコードでは、newItemという変数に新しく作成された項目を代入しています。
  2. この項目は、後続のコードでタイトルや状態などの情報を設定し、データベースに保存される予定です。

要するに、この行のコードは、Core DataのContextを使用して新しい項目を作成しています。これにより、データモデルに基づいたオブジェクトが生成され、その後の処理で必要なデータを設定してデータベースに保存する準備が整います。

解説3:try context.save()
try context.save()
  • context.save():
    Contextの変更をデータベースに保存するためのメソッド呼び出しです。
  • save()メソッドは、Contextに含まれる変更をデータベースに永続化する役割を担っています。
  • tryキーワードにより、保存操作にエラーが生じた場合にエラーがスローされます。
  • エラーがスローされると、実行が中断され、エラー処理が行われます。

要するに、この行は、Core DataのContextを使用してデータの変更をデータベースに保存するためのコードです。

CoreData:データ読み込み

次に、CoreDataからデータを読み込むコード(抜粋)を紹介します。

import UIKit
import CoreData

class TodoListViewController: UITableViewController {
    
    var itemArray = [Item]()

    //AppDelegateにアクセスし、CoreDataのコンテキストを取得
    let context = (UIApplication.shared.delegate as! AppDelegate)
        .persistentContainer.viewContext

    override func viewDidLoad() {
        super.viewDidLoad()
        loadItems()
    }

    func loadItems() {
//解説1:
        let request : NSFetchRequest<Item> = Item.fetchRequest()
        do {
//解説2:
            itemArray = try context.fetch(request)
        } catch {
            print("Error fetching data from context \(error)")
        }
    }
}
解説1:let request…
let request : NSFetchRequest<Item> = Item.fetchRequest()
  • requestという変数を定義し、NSFetchRequest<Item>型として型アノテーションが指定されています。
  • これにより、request変数はNSFetchRequestのインスタンスであり、そのジェネリック型パラメータとしてItemクラス(エンティティ)が指定されていることを示しています。

ジェネリック型とは、あるクラスや構造体が、複数の異なる型で動作できるようにするための機能です。NSFetchRequest<Item>というようにジェネリック型を指定することで、NSFetchRequestクラスをItemクラスのデータを取得するために使用することができます。

  1. Item.fetchRequest()は、Itemエンティティに対してデータベース内でリクエストを行うためのメソッドです。
  2. このメソッドを呼び出すことで、NSFetchRequestオブジェクトが作成されます。
  3. NSFetchRequestは、Core Dataに対してデータを取得するための柔軟な方法を提供するクラスです。
  4. このクラスを使用することで、特定のエンティティ(テーブル)からデータを取得するための条件やソートの設定などを行うことができます。

具体的には、このコードではItem.fetchRequest()を使用して、Itemエンティティに対してリクエストを行っています。これにより、データベース内のItemエンティティの全てのデータを取得するためのリクエストが作成されます

解説2:itemArray = try context.fetch(request)
 itemArray = try context.fetch(request)
  • try context.fetch(request)が呼び出され、リクエストを実行してデータを取得します。
  • fetch()メソッドは、リクエストに基づいてデータを取得し、配列として返します。
  • 取得したデータは、itemArrayに代入されます。
  • このようにして、Core Dataから取得したデータをアプリ内で利用できるようになります。
  • もしエラーが発生した場合は、catch節の中のprint("Error fetching data from context \(error)")が実行され、エラーメッセージが表示されます。

CoreData:データ削除

次に、CoreDataのデータを削除するコード(抜粋)を紹介します。

import UIKit
import CoreData

class TodoListViewController: UITableViewController {
    
    var itemArray = [Item]()
    //AppDelegateにアクセスし、CoreDataのコンテキストを取得
    let context = (UIApplication.shared.delegate as! AppDelegate)
        .persistentContainer.viewContext
  
//MARK - TableView Delegate Methods
    
    override func tableView(_ tableView: UITableView, 
        didSelectRowAt indexPath: IndexPath) {
//解説1:       
        context.delete(itemArray[indexPath.row])
        itemArray.remove(at: indexPath.row)

        saveItems()
    }
 
    func saveItems() {
        do {
            try context.save()
        } catch {
            print("Error saving context \(error)")
        }
        tableView.reloadData()
    }
}
解説1:context.delete…
context.delete(itemArray[indexPath.row])
itemArray.remove(at: indexPath.row)
  • context.delete(itemArray[indexPath.row]):
    指定されたindexPathに基づいて、Core DataのContextから項目を削除しています。
    delete()メソッドは、指定されたオブジェクトをContextから削除するためのメソッドです。
  • itemArray.remove(at: indexPath.row):
    itemArrayから指定されたindexPathの位置(行)にある項目を削除しています。
    これにより、配列から項目が削除されます。

要するに、このコードは、指定されたindexPathに基づいて、Core DataのContextと配列から項目を削除する処理を示しています。データベースと配列の両方から項目を削除することで、データの整合性を保ちながら削除操作が行われます。

STEP
オブジェクトライブラリから「SearchBar」を検索し、配置する

配置する場所は、ナビゲーションバーのすぐ下、すなわちテーブルビューの上です。

STEP
ビューコントローラーを SearchBarのデリゲートに設定する

Ctrlキーを押しながら、黄色いアイコンまでドラッグします。

このOutletsにはデリゲートがあることが確認できます。

STEP
SearchBarのコードを作成

文字検索し、その内容をテーブルビューに表示するコード(抜粋)を紹介します。

import UIKit
import CoreData

class TodoListViewController: UITableViewController {
    
    var itemArray = [Item]()

//解説1: 
    func loadItems(with request: NSFetchRequest<Item> = Item.fetchRequest()) {

        do {
            itemArray = try context.fetch(request)
        } catch {
            print("Error fetching data from context \(error)")
        }
        
        tableView.reloadData()
    }
}

//MARK: - Search bar methods
解説2:
extension TodoListViewController: UISearchBarDelegate {

解説3:    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        
        let request : NSFetchRequest<Item> = Item.fetchRequest()
        
        request.predicate = NSPredicate(format: "title CONTAINS %@",
                                    searchBar.text!)
        
        request.sortDescriptors = [NSSortDescriptor(key: "title",
                                              ascending: true)]
        loadItems(with: request)
    }
}
解説1:func loadItems(with request: NSFetchRequest = Item.fetchRequest())
func loadItems(with request: 
    NSFetchRequest<Item> = Item.fetchRequest()) {
  • このメソッドは、指定されたリクエストに基づいてデータをロードし、表示を更新する役割を持っています。
  • 引数としてrequestがあり、NSFetchRequest<Item>型として宣言されています。
  • この引数は、データの取得に関するリクエストを指定するために使用されます。
  • デフォルト値としてItem.fetchRequest()が指定されており、この場合はItemエンティティのデータを全て取得するリクエストが使用されます
解説2:extension TodoListViewController…
extension TodoListViewController: 
    UISearchBarDelegate {

TodoListViewControllerクラスを拡張してUISearchBarDelegateプロトコルに準拠させるためのエクステンションを定義しています。これにより、サーチバーの操作に関連するデリゲートメソッドを実装することができます。

解説1:func searchBarSearchButtonClicked…の解説
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        
        let request : NSFetchRequest<Item> = Item.fetchRequest()
        
        request.predicate = NSPredicate(format: "title CONTAINS %@",
                                    searchBar.text!)
        
        request.sortDescriptors = [NSSortDescriptor(key: "title",
                                              ascending: true)]
        loadItems(with: request)
    }

サーチバーの検索ボタンが押された時に呼び出されます。メソッド内の処理は以下の通りです

  • let request : NSFetchRequest<Item> = Item.fetchRequest():
    Item.fetchRequest()を使用してrequestという名前のNSFetchRequest<Item>オブジェクトを作成します。これは、Core Dataからデータを取得するためのリクエストを表します。
  • request.predicate = NSPredicate(format: "title CONTAINS %@",searchBar.text!):request.predicateを使用して、フィルタ条件(Predicate)を設定します
    この例では、"title CONTAINS %@"というフィルタ条件を指定しています。
    %@は検索バーに入力されたテキストで置き換えられます。
    この条件は、項目のタイトル(title)の中に、指定されたテキスト(searchBar.text!)が含まれる場合にデータをフィルタリングします。
  • request.sortDescriptors= [NSSortDescriptor(key: "title",ascending: true)]request.sortDescriptorsを使用して、データのソート順を設定します。この例では、タイトルを昇順にソートするように指定しています。
  • loadItems(with: request)
    loadItems(with: request)を呼び出して、指定されたリクエストに基づいてデータをロードします。

検索文字を消した時に、元のリストを表示する

検索文字を消した時に、元のリストを表示するコード(抜粋)を紹介します。

func searchBar…の解説
func searchBar(_ searchBar: UISearchBar, 
    textDidChange searchText: String) {
    if searchBar.text?.count == 0 {
        loadItems()
            
        DispatchQueue.main.async {
            searchBar.resignFirstResponder()
        }
    }
}
  1. func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String):searchBar(_:textDidChange:)メソッドのシグネチャを示しています。
    このメソッドは、サーチバーのテキストが変更された際に呼び出されます

メソッド内の処理は以下の通りです:

  1. if searchBar.text?.count == 0:
    サーチバーのテキストが空かどうかをチェックしています
    もし空であれば、条件式がtrueとなり、中括弧内の処理が実行されます。
  2. loadItems():
    データをロードします。
    loadItems()デフォルト値としてItem.fetchRequest()が指定されており、この場合はItemエンティティのデータを全て取得するリクエストが使用されます
  3. DispatchQueue.main.async:
    非同期でメインキュー上で実行されるクロージャ(closure)を定義しています。
    その中で、searchBar.resignFirstResponder()を呼び出して、サーチバーのフォーカスを解除します。この処理により、キーボードが閉じられます。
DispatchQueue.main.asyncを詳しく解説

DispatchQueueは、タスクや処理をキューに追加し、適切なスレッドで実行するためのクラスです。mainはメインキューを指し、メインスレッド上で実行されるキューです。
asyncメソッドは、指定したキューで非同期に処理を実行するためのメソッドです。メソッドの引数としてクロージャ(closure)が渡されます。クロージャは、処理の実行内容を指定する無名の関数です。

具体的には、DispatchQueue.main.asyncによって非同期でメインキュー上で実行されるクロージャが定義されています。このクロージャ内では、以下の処理が行われます:

この非同期の処理により、メインキュー(メインスレッド)上で実行されるため、UIの操作や更新がスムーズに行われます。UIの操作はメインスレッドで行う必要があり、非同期で実行されることで他の処理と並行して実行されるため、アプリのレスポンス性が向上します。

新画面(カテゴリー画面)を作成し、2画面にする。

カテゴリー画面(ビュー)を作成し、コードとリンクさせる

STEP
新しくテーブルビューを作成する

オブジェクトライブラリーから新規にTableViewController(以下、ビューコントローラー(カテゴリー画面))を作成する。

ナビゲーションコントローラーとTodoリストコントローラーのリンクを削除する

STEP
ナビゲーションコントローラーから新しいビューコントローラーへリンクの作成

新しいビューコントローラー(カテゴリー画面)をナビゲーションコントローラーとTodoリストコントローラーの間に配置します。

ナビゲーションコントローラーから新しいビューコントローラーへリンクを作成します。「root view controller」を選択します。

root view controller」とは、セグエ(segue)によって画面遷移を行った際の、最初に表示されるビューコントローラ(view controller)を指します。

STEP
新しいビューコントローラーからTodoリストコントローラーへリンクを作成します。

新しいビューコントローラーからTodoリストコントローラーへリンクを作成します。「Show」を選択します。

Show」セグエは、通常、ユーザーがタップやボタンを押すなどのアクションに対して、次の画面を表示するために使用されます。ビューコントローラ間の移動を視覚的に表現する際によく使用されるセグエです。

Identifierは、「goToitems」に設定します。

STEP
新しいビューコントローラーのビューコントローラークラス(ファイル)を作成する

ファイルの新規作成からCocoa Touchクラスを選択し、クラス名「CategoryViewController」とします。

新しいビューコントローラーに対応するクラスに、作成したビューコントローラークラス(ファイル)「CategoryViewController」を設定します。

STEP
新しいビューコントローラーのプロトタイプセルに識別子を設定する

新しいビューコントローラーのプロトタイプセルに識別子「CategoryCell」を設定する

STEP
ナビゲーションバー右上に追加ボタンを作成
STEP
追加ボタンのIBActionボタンを作成
STEP
タイトルを変更

カテゴリーエンティティの設定

カテゴリーのエンティティの設定を行います。

次に、従来からあるItemエンティティとのリレーションシップを設定します。
これにより、新しいCategoryを作成すると、多くのItemとManaged Objectとしてリンクさせることができるようになりました。

Itemエンティティ
Categoryエンティティ

設定した内容をまとめると以下のようになります。

エンティティItemCategory
Attributestitle
done
name
RelationshipparentCategoryitems
TypeTo OneTo Many
DestinationCategoryItem
InverseitemsparentCategory
  • エンティティ
    Core Dataモデル内のデータの種類を表すもの
  • Attributes
    エンティティが持つ個々のデータフィールドを表します
  • Relationship
    エンティティ間の関係を表します
  • Type
    エンティティ間の関係の種類を指定します
    To One:1つのエンティティが他のエンティティと1つの関連を持つことを示します
    To Many:1つのエンティティが他のエンティティと複数の関連を持つことを示します
  • Destination
    関係を持つ別のエンティティを指します
  • Inverse
    関係の対象であるエンティティの関連を逆方向に参照するためのプロパティを指します

カテゴリー画面のコードを作成(カテゴリー追加)

Todoリスト画面と同様に以下のコードを作成しました。
import UIKit
import CoreData

class CategoryViewController: UITableViewController {
    
    var categories = [Category]()
    //AppDelegateにアクセスし、CoreDataのコンテキストを取得
    let context = (UIApplication.shared.delegate as! AppDelegate)
        .persistentContainer.viewContext
    
    override func viewDidLoad() {
        super.viewDidLoad()

        loadCategories()
    }

    //MARK: - TableView Datasource Methods
    override func tableView(_ tableView: UITableView, 
        numberOfRowsInSection section: Int) -> Int {
        return categories.count
    }
    
    override func tableView(_ tableView: UITableView, 
        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "CategoryCell", 
            for: indexPath)
        
        cell.textLabel?.text = categories[indexPath.row].name
        
        return cell
    }
    
    //MARK: - Data Manipulation Methods
    func saveCategories() {
        do {
            try context.save()
        } catch {
            print("Error saving context \(error)")
        }
        tableView.reloadData()
    }
    
    func loadCategories() {       
        let request : NSFetchRequest<Category> 
            = Category.fetchRequest()

        do {
            categories = try context.fetch(request)
        } catch {
            print("Error loading categories \(error)")
        }      
        tableView.reloadData()
    }
    
    //MARK: - Add New Categories
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
        var textField = UITextField()
        
        let alert = UIAlertController(
            title: "Add New Category", 
            message: "", preferredStyle: .alert)
        let action = UIAlertAction(
            title: "Add", 
            style: .default) { (action) in

            //Addボタンが押された時の処理
            //Core Dataのコンテキストを使用して新しい項目を作成
            let newCategory = Category(context: self.context)
            newCategory.name = textField.text!
            self.categories.append(newCategory)
            
            self.saveCategories()
            
        }
        alert.addAction(action)
        
        // UIAlertActionをUIAlertControllerに追加
        alert.addTextField { (field) in
            textField = field
            textField.placeholder = "Add a  new Category"
        }
        // UIAlertControllerを表示
        present(alert, animated: true, completion: nil)
    }

カテゴリー選択→Todoリスト画面に遷移し検索データを表示する

カテゴリーを選択すると、Todoリスト画面に遷移し、選択されたカテゴリに対応するTodoリストを表示します。
さらに、その中からSearchBarで検索したデータを表示するコードを作成します。

1.カテゴリー画面のコード作成(抜粋)

カテゴリー画面のコード(全文)はこちら
import UIKit
import CoreData

class CategoryViewController: UITableViewController {
    
    var categories = [Category]()
    
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    override func viewDidLoad() {
        super.viewDidLoad()
        
        loadCategories()

    }
    
    //MARK: - TableView Datasource Methods
    
    override func tableView(_ tableView: UITableView, 
        numberOfRowsInSection section: Int) -> Int {
        
        return categories.count
        
    }
    
    override func tableView(_ tableView: UITableView, 
        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
        
        cell.textLabel?.text = categories[indexPath.row].name
        
        return cell
        
    }

    
    //MARK: - TableView Delegate Methods
    
    override func tableView(_ tableView: UITableView, 
        didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "goToItems", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let destinationVC = segue.destination as! TodoListViewController
        
        if let indexPath = tableView.indexPathForSelectedRow {
            destinationVC.selectedCategory = categories[indexPath.row]
        }
    }
    
    //MARK: - Data Manipulation Methods
    
    func saveCategories() {
        do {
            try context.save()
        } catch {
            print("Error saving category \(error)")
        }
        
        tableView.reloadData()
        
    }
    
    func loadCategories() {
        
        let request : NSFetchRequest<Category> = Category.fetchRequest()
        
        do{
            categories = try context.fetch(request)
        } catch {
            print("Error loading categories \(error)")
        }
       
        tableView.reloadData()
        
    }
    
    
    //MARK: - Add New Categories

    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
        
        var textField = UITextField()
        
        let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
        
        let action = UIAlertAction(title: "Add", style: .default) { (action) in
            
            let newCategory = Category(context: self.context)
            newCategory.name = textField.text!
            
            self.categories.append(newCategory)
            
            self.saveCategories()
            
        }
        
        alert.addAction(action)
        
        alert.addTextField { (field) in
            textField = field
            textField.placeholder = "Add a new category"
        }
        
        present(alert, animated: true, completion: nil)
        
    }
}
…didSelectRowAt…の解説
override func tableView(_ tableView: UITableView, 
    didSelectRowAt indexPath: IndexPath) {
    performSegue(
        withIdentifier: "goToItems", 
        sender: self)
}

テーブルビューのセルが選択された時に呼び出されるデリゲートメソッドです。

  • performSegue(withIdentifier: "goToitems", sender: self)の行では、指定した識別子(”goToitems“)を持つセグエを実行します。これにより、セルが選択された後に指定のセグエが実行されることになります。
override func prepare(for segue: UIStoryboardSegue, sender: Any?)の解説
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let destinationVC = 
        segue.destination as! TodoListViewController
        
    if let indexPath = tableView.indexPathForSelectedRow {
        destinationVC.selectedCategory = 
            categories[indexPath.row]
    }
}

セグエが実行される前に呼び出されるデリゲートメソッドです。

  • let destinationVC = segue.destination as! TodoListViewController:
    セグエの目的地ビューコントローラを取得しています。
    ここではTodoListViewControllerという型が前提とされています。
  • if let indexPath = tableView.indexPathForSelectedRow:
    選択されたセルのインデックスパスを取得しています。
    このコードはオプショナルバインディングを使用しており、選択されたセルのインデックスパスが存在する場合のみ処理を実行します。
  • destinationVC.selectedCategory = categories[indexPath.row]:
    目的地ビューコントローラのselectedCategoryプロパティに、選択されたセルのカテゴリを渡しています。
    ここでは、categories配列から選択されたセルのインデックスに対応するカテゴリを取得しています。

2.Todoリスト画面のコード作成(抜粋)

Todoリスト画面のコード(全文)はこちら
import UIKit
import CoreData

class TodoListViewController: UITableViewController {
    
    var itemArray = [Item]()
    
    var selectedCategory : Category? {
        didSet{
            loadItems()
        }
    }
    
    let context = (UIApplication.shared.delegate as! AppDelegate)
        .persistentContainer.viewContext
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))

        
    }
    
    //MARK: - Tableview Datasource Methods
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemArray.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath)
        
        let item = itemArray[indexPath.row]
        
        cell.textLabel?.text = item.title
        
        //Ternary operator ==>
        // value = condition ? valueIfTrue : valueIfFalse
        
        cell.accessoryType = item.done ? .checkmark : .none
        
        return cell
    }
    
    //MARK: - TableView Delegate Methods
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        
        itemArray[indexPath.row].done = !itemArray[indexPath.row].done

        saveItems()
        
        tableView.deselectRow(at: indexPath, animated: true)
        
    }
    
    //MARK: - Add New Items
    
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
        
        var textField = UITextField()
        
        let alert = UIAlertController(title: "Add New Todoey Item", message: "", preferredStyle: .alert)
        
        let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
            //what will happen once the user clicks the Add Item button on our UIAlert
            
            
            let newItem = Item(context: self.context)
            newItem.title = textField.text!
            newItem.done = false
            newItem.parentCategory = self.selectedCategory
            self.itemArray.append(newItem)
            
            self.saveItems()
        }
        
        alert.addTextField { (alertTextField) in
            alertTextField.placeholder = "Create new item"
            textField = alertTextField
            
        }
        
        
        alert.addAction(action)
        
        present(alert, animated: true, completion: nil)
        
    }
    
    //MARK - Model Manupulation Methods
    
    func saveItems() {
        
        do {
          try context.save()
        } catch {
           print("Error saving context \(error)")
        }
        
        self.tableView.reloadData()
    }
    
    func loadItems(with request: NSFetchRequest<Item> = 
        Item.fetchRequest(), predicate: NSPredicate? = nil) {
        
        let categoryPredicate = 
            NSPredicate(format: "parentCategory.name MATCHES %@", 
            selectedCategory!.name!)
        
        if let addtionalPredicate = predicate {
            request.predicate = 
            NSCompoundPredicate(andPredicateWithSubpredicates: 
            [categoryPredicate, addtionalPredicate])
        } else {
            request.predicate = categoryPredicate
        }

        
        do {
            itemArray = try context.fetch(request)
        } catch {
            print("Error fetching data from context \(error)")
        }
        
        tableView.reloadData()
        
    }
    
}

//MARK: - Search bar methods

extension TodoListViewController: UISearchBarDelegate {
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {

        let request : NSFetchRequest<Item> = Item.fetchRequest()
    
        let predicate = NSPredicate(format: "title CONTAINS[cd] %@", searchBar.text!)
        
        request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
        
        loadItems(with: request, predicate: predicate)
        
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar.text?.count == 0 {
            loadItems()
            
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
          
        }
    }
}
var selectedCategory…の解説
var selectedCategory : Category? {
    didSet {
        loadItems()
    }
}
  • このコードは、selectedCategoryという変数を定義しています。
  • selectedCategoryは、Category型のオプショナルな変数です。

didSetブロックを持つプロパティの変更監視(property observer)として使われています。didSetブロックは、この変数が値が変更された後に実行されるコードを指定するために使用されます。

  • selectedCategoryの値が変更された後
    didSetブロックが呼び出される
    loadItems()メソッドが実行
@IBAction func addButtonPressedの解説
@IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
    let newItem = Item(context: self.context)
    newItem.title = textField.text!
    newItem.done = false
    newItem.parentCategory = self.selectedCategory
    self.itemArray.append(newItem)
}
  • newItem.parentCategory = self.selectedCategoryのコードは、新しいToDoアイテム(Item)を特定のカテゴリ(Category)に関連付けるためのものです。
  • parentCategoryというプロパティは、ToDoアイテム(Item)とカテゴリ(Category)の間のリレーションシップを表します。
  • self.selectedCategoryには、現在選択されているカテゴリが格納されています。
  • 新しいToDoアイテムを作成する際に、そのparentCategoryプロパティに選択されたカテゴリを設定することで、ToDoアイテムが特定のカテゴリに属していることが示されます。
func loadItems… の解説
func loadItems(with 
    request: NSFetchRequest<Item> = Item.fetchRequest(), 
    predicate: NSPredicate? = nil) {

    let categoryPredicate = 
        NSPredicate(format: "parentCategory.name MATCHES %@", 
        selectedCategory!.name!)
        
    if let addtionalPredicate = predicate {
        request.predicate = 
            NSCompoundPredicate(andPredicateWithSubpredicates: 
            [categoryPredicate, addtionalPredicate])
    } else {
        request.predicate = categoryPredicate
    }
}
  • loadItemsメソッドは、指定されたリクエストpredicateに基づいてデータをロードし、テーブルビューの表示を更新します。
  • categoryPredicateは、選択されたカテゴリに対応するアイテムを取得するためのpredicateです。
  • request.predicateには、引数のpredicatecategoryPredicateを組み合わせた複合述語が設定されます。
  • もしpredicateが存在する場合は、両方の述語を結合して設定し、存在しない場合はcategoryPredicateのみを設定します。

完成した画面紹介

STEP
カテゴリー選択画面
STEP
Todoリスト画面
STEP
SearchBarの検索結果を表示

この記事が気に入ったら
いいね または フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次