Search code examples
swiftuitableviewcore-datansfetchedresultscontroller

Core Data: Showing NSFetchRequestResult Search Results with UITableView


I've been playing Core Data for the past several days. I'm fetching data with NSFetchedResultsController. My entity has such attributes as age (Int), firstName (String), lastName (String), uuid (String). I am able to insert a new record to the database. I am able to delete a record. I am also able to edit a record. What I cannot do is to show a search result with the table.

class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // MARK: - Instance variables
    private let persistentContainer = NSPersistentContainer(name: "Profiles") // core data model file (.xcdatamodeld)
    var managedObjectContext: NSManagedObjectContext?

    // MARK: - IBOutlets
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchButton: UIButton!

    // MARK: - IBActions
    @IBAction func searchTapped(_ sender: UIButton) {
        searchRecords()
    }

    // MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        // loading persistentContainer //
        persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
            if let error = error {
                print("Unable to Load Persistent Store")
                print("\(error), \(error.localizedDescription)")
            } else {
                do {
                    try self.fetchedResultsController.performFetch()
                } catch {
                    let fetchError = error as NSError
                    print("Unable to Perform Fetch Request")
                    print("\(fetchError), \(fetchError.localizedDescription)")
                }
            }
        }
    }
    // MARK: - Life cycle

    // MARK: - fetchedResultsController(controller with the entity)
    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
        // Create Fetch Request with Entity
        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()

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

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

        // Configure Fetched Results Controller
        fetchedResultsController.delegate = self
        return fetchedResultsController
    }()
    // MARK: - fetchedResultsController

    // MARK: - TableView
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let people = fetchedResultsController.fetchedObjects else { return 0 }
        return people.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ProfileTableViewCell
        let person = fetchedResultsController.object(at: indexPath)

        // Configure Cell
        cell.firstLabel.text = person.firstName
        cell.lastLabel.text = person.lastName
        cell.ageLabel.text = String(person.age)
        return cell
    }
    // MARK: - TableView

    // MARK: - Showing search result
    func searchRecords() {
        let context = self.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
        let predicate = NSPredicate(format: "firstName CONTAINS[c] %@", "Sandra")
        fetchRequest.predicate = predicate
        do {
            try fetchedResultsController.performFetch()
            /*
            let result = try context.fetch(fetchRequest)
            if (result.count > 0) {
                print(result.count)
            }
            */
        } catch {
            print("bad")
        }
    }
    // MARK: - Showing search result
}

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

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

    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 {
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }
            break;
        default:
            tableView.reloadData()
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    }
}

If I run searchRecords(), result.count will correctly return the number of records. But the table stays the same. So how can I show my search result with the table? Thanks.


Solution

  • Replace your tableview datasource with

    // MARK: - Table View
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return self.fetchedResultsController.sections?.count ?? 0
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionInfo = self.fetchedResultsController.sections![section]
        return sectionInfo.numberOfObjects
    }
    

    Hope it is helpful