Reputation: 317
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
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
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