Swift初心者です。
現在、iOSアプリ作成の練習として家計簿アプリを作成しています。

これまで、

  • CoreDataに「年月日、カテゴリ(食費など)、費用」を保存。
  • 保存したデータをTableViewに表示。
  • データを追加したらfetchedResultsControllerを用いてリアルタイムでTableViewに反映。
  • TableViewのセルをタップするとその出費データの修正ができる(それもリアルタイムで反映)。

というところまで実装することができました。

しかし、指定された月の出費データのみをTableViewに表示する方法がわかりません。
CoreDataに保存されている出費データすべてをTableViewに表示してしまいます。

ようするに、今月のデータのみをCoreDataから持ってきてTableViewに表示する、先月のデータのみを見たいときは、何らかの方法で画面を切り替えて先月のデータのみで構成されたTableViewを表示する、などということができません。

TableViewに特定の月のデータだけを表示する方法はありませんか?
(現在モデルとエンティティを1つずつ使用していますが、複数のモデルまたはエンティティを用意するべきでしょうか? その場合はどのようにしてモデル・エンティティを切り替えればいいでしょうか? それとも、CoreDataに保存されているデータのうち、特定のデータのみを選択してTableViewに表示する方法はありますか?)

試したこと

  • モデルを月ごとに用意し、TableViewごとにモデルを切り替えれば良いのでは、と思い試してみましたが、「Spending」というエンティティを共通にしたため、Spendingというクラスは曖昧だからダメと言われました。かといってエンティティを別名にすると、fetchedResultsControllerを作成するコードを複数用意しないといけないと思うので、スマートではないかな、と思いました(まだ試していません)。
  • 表示したい月以外のデータを表示するセルは非表示にすればいいと思い、TableViewのデータソースでそれを試しましたが、非表示にしたデータの部分が真っ白になるだけで、セルごと消えてくれませんでした。

以下に、「ViewController.swift」のコードを記載しておきますので、何卒よろしくお願いします。
 
 

コードとその軽い解説

CoreDataのモデルは「Spendings.xcdatamodeld」というモデルで、「Spending」というエンティティが「year」「month」「day」「category」「cost」という属性を持っています。
年月日とカテゴリはそれぞれPickerViewで選択し、それらをそのDelegateでmyDate[]やmyCategoryという変数に代入しています。
そして、それらのデータをCoreDataへ保存しています。
それらのデータ追加処理は「AddSpendingViewController.swift」という別ファイルで書いていますが、そちらはTalbeViewと関係がなさそうなので記載していません(そちらも見てみないと何とも言えないようでしたら、追記します)。
他に必要なコードがありましたら記載しますので、よろしくお願いします。

import UIKit
import CoreData

class ViewController: UIViewController {

    // MARK: - Properties

    private let segueAddSpendingViewController = "SegueAddSpendingViewController"
    private let segueEditSpendingViewController = "SegueEditSpendingViewController"

    // MARK: -

    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!

    // MARK: -

    private let persistentContainer = NSPersistentContainer(name: "Spendings")

    // MARK: -

    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Spending> = {
        // Create Fetch Request
        let fetchRequest: NSFetchRequest<Spending> = Spending.fetchRequest()

        // Configure Fetch Request
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "day", ascending: true)]

        // Create Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: #keyPath(Spending.category), cacheName: nil)

        // Configure Fetched Results Controller
        fetchedResultsController.delegate = self

        return fetchedResultsController
    }()

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        persistentContainer.loadPersistentStores { (NSPersistentStoreDescription, error) in
            if let error = error {
                print("Unable to Load Persistent Store")
                print("\(error), \(error.localizedDescription)")
            } else {
                self.setupView()

                do {
                    try self.fetchedResultsController.performFetch()
                } catch {
                    let fetchError = error as NSError
                    print("Unable to Perform Fetch Request")
                    print("\(fetchError), \(fetchError.localizedDescription)")
                }

                self.updateView()
            }
        }

        NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: Notification.Name.UIApplicationDidEnterBackground, object: nil)
    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let destinationViewController = segue.destination as? AddSpendingViewController else { return }

        // Configure View Controller
        destinationViewController.managedObjectContext = persistentContainer.viewContext

        if let indexPath = tableView.indexPathForSelectedRow, segue.identifier == segueEditSpendingViewController {
            // Configure View Controller
            destinationViewController.spending = fetchedResultsController.object(at: indexPath)
        }
    }

    // MARK: - View Methods

    private func setupView() {
        setupMessageLabel()
        updateView()
    }

    fileprivate func updateView() {
        var hasSpendings = false

        if let spendings = fetchedResultsController.fetchedObjects {
            hasSpendings = spendings.count > 0
        }

        tableView.isHidden = !hasSpendings
        messageLabel.isHidden = hasSpendings

        activityIndicatorView.stopAnimating()
    }

    // MARK: -

    private func setupMessageLabel() {
        messageLabel.text = "今月の出費はまだありません。"
    }

    // MARK: - Notification Handling

    func applicationDidEnterBackground(_ notification: Notification) {
        do {
            try persistentContainer.viewContext.save()
        } catch {
            print("Unable to Save Changes")
            print("\(error), \(error.localizedDescription)")
        }
    }
}

extension ViewController: NSFetchedResultsControllerDelegate {

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()

        updateView()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch (type) {
        case .insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: [indexPath], with: .fade)
            }
            break;
        case .delete:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            break;
        case .update:
            if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) as? SpendingTableViewCell {
                configure(cell, at: indexPath)
            }
            break;
        case .move:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            if let newIndexPath = newIndexPath {
                tableView.insertRows(at: [newIndexPath], with: .fade)
            }
            break;
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        switch type {
        case .insert:
            tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
            tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        default:
            break;
        }
    }

    func configure(_ cell: SpendingTableViewCell, at indexPath: IndexPath) {
        // Fetch Spending
        let spending = fetchedResultsController.object(at: indexPath)

        // Configure Cell

        cell.dayLabel.text = String(spending.day) + "日"
        cell.costLabel.text = String(spending.cost) + "円"

    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        guard let sections = fetchedResultsController.sections else { return 0 }
        return sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let sectionInfo = fetchedResultsController.sections?[section] else {
            fatalError("Unexpected Section")
        }
        return sectionInfo.numberOfObjects
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        guard let sectionInfo = fetchedResultsController.sections?[section] else {
            fatalError("Unexpected Section")
        }
        return sectionInfo.name
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: SpendingTableViewCell.reuseIdentifier, for: indexPath) as? SpendingTableViewCell else {
            fatalError("Unexpected Index Path")
        }

        // Configure Cell
        configure(cell, at: indexPath)

        return cell
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // Fetch Spending
            let spending = fetchedResultsController.object(at: indexPath)

            // Delete Spending
            spending.managedObjectContext?.delete(spending)
        }
    }
}