Jumpman987
Jumpman987

Reputation: 317

How can I filter my tableview that has objects in Core Data?

I'm creating a contacts app, so far I've successfully managed to save items to my tableview. I have a search bar and I want to filter out my cells by first name, I know since I'm working in Core Data I'll have to use fetchResultsController and NSPredicate. I'm having trouble figuring this stuff all out, maybe someone can help me out?

Also here is my Core Data entity, just in case.

Entity: Contact

Attributes:

I know some of the code may be incomplete, but I just need direction on where to take this. I just want the user to type a name and it will filter the cells by first name. Let me know if there is more information you need.

Now here is the code in my ContactsTableVC:

import UIKit
import CoreData

class ContactsTableVC: UITableViewController, UISearchBarDelegate, NSFetchedResultsControllerDelegate {

    @IBOutlet weak var searchBar: UISearchBar!

    var isFiltered: Bool = false

//Holds the core data model
    var persons: [Person] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        searchBar.delegate = self

        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
        self.tableView.backgroundColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0)

        fetch()
        self.tableView.reloadData()
    }

    func getContext () -> NSManagedObjectContext {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        return appDelegate.persistentContainer.viewContext
    }

    // MARK: - Searchbar
    //add fetchrequest to did ebgin editing
    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {

    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        if(searchBar.text == "") {
            isFiltered = false
        } else {
            isFiltered = true
        }
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filter(text: searchText)
    }

    // MARK: - Fetchresults controller / filtering data

    func filter(text: String) {
        //Create fetch request
        let fetchRequest = NSFetchRequest<Person>()

//        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }         replaced with getcontext
//        let managedObjectContext = appDelegate.persistentContainer.viewContext

        let entity = NSEntityDescription.entity(forEntityName: "Contact", in: getContext())
        fetchRequest.entity = entity

        let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: false)
        let sortDescriptors: [Any] = [sortDescriptor]
        fetchRequest.sortDescriptors = sortDescriptors as? [NSSortDescriptor] ?? [NSSortDescriptor]()

        if(text.characters.count > 0) {
            let predicate = NSPredicate(format: "(firstName CONTAINS[c] %@)", text)
            fetchRequest.predicate = predicate
        }

        let loadedEntities: [Person]? = try? getContext().fetch(fetchRequest)
        filteredContacts = [Any](arrayLiteral: loadedEntities) as! [Person]
        self.tableView.reloadData()
    }

    // MARK: - Data Source
    func fetch() {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedObjectContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Contact")
        do {
            persons = try managedObjectContext.fetch(fetchRequest) as! [Person] //NSManagedObject
        } catch let error as NSError {
            print("Could not fetch. \(error)")
        }
    }

    func save(firstName: String, lastName: String, dob: String, phoneNumber: String, zipCode: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedObjectContext = appDelegate.persistentContainer.viewContext
        guard let entity = NSEntityDescription.entity(forEntityName:"Contact", in: managedObjectContext) else { return }
        let person = NSManagedObject(entity: entity, insertInto: managedObjectContext)
        person.setValue(firstName, forKey: "firstName")
        person.setValue(lastName, forKey: "lastName")
        person.setValue(dob, forKey: "dateOfBirth")
        person.setValue(phoneNumber, forKey: "phoneNumber")
        person.setValue(zipCode, forKey: "zipCode")
        do {
            try managedObjectContext.save()
            self.persons.append(person as! Person) //previously just contact, no casting!
        } catch let error as NSError {
            print("Couldn't save. \(error)")
        }
    }

    func update(indexPath: IndexPath, firstName: String, lastName: String, dob: String, phoneNumber: String, zipCode: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedObjectContext = appDelegate.persistentContainer.viewContext
        let contact = persons[indexPath.row]
        contact.setValue(firstName, forKey: "firstName")
        contact.setValue(lastName, forKey: "lastName")
        contact.setValue(dob, forKey: "dateOfBirth")
        contact.setValue(phoneNumber, forKey: "phoneNumber")
        contact.setValue(zipCode, forKey: "zipCode")
        do {
            try managedObjectContext.save()
            persons[indexPath.row] = contact
        } catch let error as NSError {
            print("Couldn't update. \(error)")
        }
    }

    func delete(_ contact: NSManagedObject, at indexPath: IndexPath) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedObjectContext = appDelegate.persistentContainer.viewContext
        managedObjectContext.delete(contact)
        persons.remove(at: indexPath.row)

        //Always remember to save after deleting, updates Core Data
        do {
            try managedObjectContext.save()
        } catch {
            print("Something went wrong \(error.localizedDescription)")
        }
    }

    // MARK: - Table View Setup
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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


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

        let person = persons[indexPath.row]

        cell?.firstName?.text = person.value(forKey:"firstName") as? String
        cell?.lastName?.text = person.value(forKey:"lastName") as? String
        cell?.dob?.text = person.value(forKey:"dateOfBirth") as? String
        cell?.phoneNumber?.text = person.value(forKey:"phoneNumber") as? String
        cell?.zipCode?.text = person.value(forKey:"zipCode") as? String

        return cell!
    }

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

    // Override to support conditional editing of the table view.
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // MARK: - Navigation
    @IBAction func unwindToContactsList(segue:UIStoryboardSegue) {
        if let viewController = segue.source as? AddContactVC {
            guard let _firstName: String = viewController.firstNameLbl.text,
                let _lastName: String = viewController.lastNameLbl.text,
                let _dob: String = viewController.dateOfBirthLbl.text,
                let _phoneNumber: String = viewController.phoneNumberLbl.text,
                let _zipCode: String = viewController.zipCodeLbl.text
                else { return }
            if _firstName != "" && _lastName != "" && _dob != "" && _phoneNumber != "" && _zipCode != "" {
                if let indexPath = viewController.indexPathForContact {
                    update(indexPath: indexPath, firstName: _firstName, lastName: _lastName, dob: _dob, phoneNumber: _phoneNumber, zipCode: _zipCode)
                    print("Any updates?")
                } else {
                    save(firstName: _firstName, lastName: _lastName, dob: _dob, phoneNumber: _phoneNumber, zipCode: _zipCode)
                    print("added to tableview") //this runs twice for some reason...
                }
            }
            tableView.reloadData()
        } else if let viewController = segue.source as? EditContactVC {
            if viewController.isDeleted {
                guard let indexPath: IndexPath = viewController.indexPath else { return }
                let person = persons[indexPath.row]
                delete(person, at: indexPath)
                tableView.reloadData()
            }
        }
    }
}

Upvotes: 3

Views: 2082

Answers (2)

mrfour
mrfour

Reputation: 1258

Here is a sample code to achieve your goal with NSFetchedResultsController. I omitted some irrelevant codes.

class ContactViewController: UITableViewController {
    let fetchedResultsController: NSFetchedResultsController<Contact>!

    func searchTextFieldDidEditingChanged(_ textField: UITextField) {
        let text = textField.text ?? ""
        refetch(with: text)
    }

    // The key is you need change the predicate when searchTextField's 
    // value changed, and invoke proformFetch() again
    func refetch(with text: String) {
        let predicate = NSPredicate(format: "firstName CONTAINS %@", text)
        fetchedResultsController.fetchRequest.predicate = predicate

        do {
            try self.fetchedResultsController.performFetch()
            tableView.reloadData()
        } catch let error as NSError {
            loggingPrint("Error: \(error.localizedDescription)")
        }
    }
}

// MARK: - Table datasource

extension ContactViewController {
    override func numberOfSections(in tableView: UITableView) -> Int {
        return fetchedResultsController.sections!.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionInfo = fetchedResultsController.sections![section]
        return sectionInfo.numberOfObjects
    }

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

        let contact = fetchedResultsController.object(at: indexPath)
        cell.contact = contact

        return cell!
    }
}

Upvotes: 2

Mihir Thanekar
Mihir Thanekar

Reputation: 518

You can use the sortDescriptors property of NSPredicate to filter the results of your fetch request.

Check the link for more info: How to sort a fetch in Core Data

Upvotes: 1

Related Questions