Swift3/Xcode8でCoreDataに保存したデータを特定データのみTableViewに表示する方法
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)
}
}
}