Michael
Michael

Reputation: 809

Grouping Data in TableView from Core Data in Swift 4

I am having trouble finding out how to group my sections by a property in my Core Data database. This is what my DB looks like here. I am trying to group my tableView by the dueDate property. I have loaded up my Attributes in an array and that is how they are displayed. I plan on customizing the headers as well, so I would like to use the standard tableView methods. Here is the code from my ViewController.

import UIKit
import CoreData

class MainTableViewController: UITableViewController {

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    var taskArray = [Task]()

    override func viewDidAppear(_ animated: Bool) {
        loadData()
    }

    // MARK: - Table view functions
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return taskArray.count
    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Date"
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 65.00
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell

        cell.nameLabel.text = taskArray[indexPath.row].name ?? "Add Items"

        if taskArray[indexPath.row].dueTime == nil {
            cell.timeLabel.text = ""
        } else {
            let timeFormatter = DateFormatter()
            timeFormatter.timeStyle = .short
            cell.timeLabel.text = timeFormatter.string(from: taskArray[indexPath.row].dueTime!)
        }

        return cell
    }

    // MARK: Add New Task
    @IBAction func addButtonPressed(_ sender: Any) {
        performSegue(withIdentifier: "newTaskSegue", sender: self)
    }


    // MARK: Save & Load Data
    func saveData() {
        do {
            try context.save()
        } catch {
            print("Error saving context \(error)")
        }
        tableView.reloadData()
    }

    func loadData() {
        let request : NSFetchRequest<Task> = Task.fetchRequest()
        let sort = NSSortDescriptor(key: "dueDate", ascending: false)
        let sort2 = NSSortDescriptor(key: "dueTime", ascending: false)
        request.sortDescriptors = [sort, sort2]

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

}

Any help would be much appreciated. Thanks!

Upvotes: 1

Views: 1077

Answers (1)

FJ de Brienne
FJ de Brienne

Reputation: 243

You can easily group your data using NSFetchedResultsController. One parameter in the instantiation of NSFetchedResultsController specifically allows you to group your results into sections by passing the keyPath of an attribute that constitutes the predicate for section grouping.

Apple's documentation explains this pretty clearly, with example code:

override func numberOfSections(in tableView: UITableView) -> Int {
    if let frc = <#Fetched results controller#> {
        return frc.sections!.count
    }
    return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    guard let sections = self.<#Fetched results controller#>?.sections else {
        fatalError("No sections in fetchedResultsController")
    }
    let sectionInfo = sections[section]
    return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = <#Get the cell#>
    guard let object = self.<#Fetched results controller#>?.object(at: indexPath) else {
        fatalError("Attempt to configure cell without a managed object")
    }
    // Configure the cell with data from the managed object.
    return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    guard let sectionInfo = <#Fetched results controller#>?.sections?[section] else {
        return nil
    }
    return sectionInfo.name
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
    return <#Fetched results controller#>?.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
    guard let result = <#Fetched results controller#>?.section(forSectionIndexTitle: title, at: index) else {
        fatalError("Unable to locate section for \(title) at index: \(index)")
    }
    return result
}

It is generally a Good Idea(tm) to use an NSFetchedResultsController when dealing with CoreData and UITableView or UICollectionView as you get handy notifications (through a NSFetchedResultsControllerDelegate) when your data changes that allow you to insert or remove cells from your displayed view.

Upvotes: 2

Related Questions