32195


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と関係がなさそうなので記載していません（そちらも見てみないと何とも言えないようでしたら、追記します）。  
他に必要なコードがありましたら記載しますので、よろしくお願いします。

[code]

    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)
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2017-01-25T06:52:16.493
32141
2017-01-27T07:28:25.657
20383
question
swift xcode swift3 uitableview coredata
Swift3/Xcode8でCoreDataに保存したデータを特定データのみTableViewに表示する方法


該当するオブジェクトが格納されているindexPathの配列を用意するというのはどうでしょうか。

[code]

    var spendingIndexesToShow = [IndexPath]()
    
[/code]

全てのオブジェクトを検査して該当するものだけをappendしていきます。  
ソートされている場合は範囲だけを保存してもいいかもしれません

配列に保存する場合は例えば以下のようになります

[code]

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return spendingIndexesToShow.count
    }
    
[/code]


CC BY-SA 3.0
2017-01-25T11:58:15.310
32153
2017-01-25T11:58:15.310
20389
32141
answer


fetch するときに predicateを指定します

[code]

    let mounth: Int = 10
    fetchRequest.predicate = NSPredicate(format: "month = %ld", month)
    
[/code]

この場合は表示する月が替わるたびにfetchする必要があります

あるいは見せるためのデータを別に用意します

[code]

    // プロパティとして
    //　表示用データ
    private(set) var data: [Any]?
    // 表示する月
    var month: Int? {
        didSet {
            if month == nil {
                data = fetchedResultsController.fetchedObjects
                return
            }
            data = fetchedResultsController.fetchedObjects.filter { $0.month == self.month
        }
    }
    var fetchedResultsControlle // ここにも同様の didSet が必要
    
[/code]


CC BY-SA 3.0
2017-01-27T07:28:25.657
32195
2017-01-27T07:28:25.657
2741
32141
answer