Reputation: 137
I am making a TableViewController with Core Data. In fact, the users can add new items to the Table View and these items are saved in Core Data.The view will display a contacts list. Everything has worked fine but I couldn't get a search bar to work. I tried a lot but I am new to swift and this is my first app. Please tell me what should I add to my code?
class ContactsViewController: UITableViewController {
@IBOutlet var searchBar: UISearchBar!
var contacts: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
fetch()
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//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 {
contacts = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch. \(error)")
}
}
func save(name: String, phoneNumber: String, dataUltimei: 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 contact = NSManagedObject(entity: entity, insertInto: managedObjectContext)
contact.setValue(name, forKey: "name")
contact.setValue(phoneNumber, forKey: "phoneNumber")
contact.setValue(dataUltimei, forKey: "dataUltimei")
do {
try managedObjectContext.save()
self.contacts.append(contact)
} catch let error as NSError {
print("Couldn't save. \(error)")
}
}
func update(indexPath: IndexPath, name:String, phoneNumber: String, dataUltimei: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let contact = contacts[indexPath.row]
contact.setValue(name, forKey:"name")
contact.setValue(phoneNumber, forKey: "phoneNumber")
contact.setValue(dataUltimei, forKey: "dataUltimei")
do {
try managedObjectContext.save()
contacts[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)
contacts.remove(at: indexPath.row)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath)
var contact = contacts[indexPath.row]
if isSearching {
contact = contacts[indexPath.row]
}
else {
contact = contacts[indexPath.row]
}
cell.textLabel?.text = contact.value(forKey:"name") as? String
}
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
do {
let contact = contacts[indexPath.row]
delete(contact, at: indexPath)
fetch()
tableView.reloadData()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
@IBAction func unwindToContactList(segue: UIStoryboardSegue) {
if let viewController = segue.source as? AddContactViewController {
guard let name: String = viewController.nameTextField.text, let phoneNumber: String = viewController.phoneNumberTextField.text, let dataUltimei: String = viewController.tabel.text else { return }
if name != "" && phoneNumber != "" {
if let indexPath = viewController.indexPathForContact {
update(indexPath: indexPath, name: name, phoneNumber: phoneNumber, dataUltimei: dataUltimei)
} else {
save(name:name, phoneNumber:phoneNumber, dataUltimei:dataUltimei)
}
}
tableView.reloadData()
} else if let viewController = segue.source as? ContactDetailViewController {
if viewController.isDeleted {
guard let indexPath: IndexPath = viewController.indexPath else { return }
let contact = contacts[indexPath.row]
delete(contact, at: indexPath)
tableView.reloadData()
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "contactDetailSegue" {
guard let navViewController = segue.destination as? UINavigationController else { return }
guard let viewController = navViewController.topViewController as? ContactDetailViewController else { return }
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let contact = contacts[indexPath.row]
viewController.contact = contact
viewController.indexPath = indexPath
}
}
}
Upvotes: 1
Views: 3737
Reputation: 12292
To add to the great answer of @ViniApp
In today's Swift you might have something like
var searchLetters: String = "" {
didSet {
runCoreDataFetchRequest()
reloadData()
}
}
so, whenever that is changed,
override var theFetchRequest: NSFetchRequest<NSFetchRequestResult> {
let r = NSFetchRequest<NSFetchRequestResult>(entityName: relevantEntityName)
let s = NSSortDescriptor(key: "id", ascending: false) // for example
r.sortDescriptors = [s]
if searchLetters != "" {
let p = NSPredicate(format: "firstname contains[c] %@", searchLetters)
r.predicate = p
}
return r
}
Note that "firstname" in the example above is literally just the name of your actual field in your actual Core Data entity one which you wish to search, so obviously use an actual field name from your actual project, instead of "firstname".
Notice the fetch request is built using searchLetters
Then, the core of your core data call would be:
var frc: NSFetchedResultsController<NSFetchRequestResult>!
func runCoreDataFetchRequest() {
frc = NSFetchedResultsController(
fetchRequest: theFetchRequest,
managedObjectContext: core.container.viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
frc.delegate = self
do {
try frc.performFetch()
} catch {
fatalError("frc fetch fail \(error)")
}
}
Notice it uses theFetchRequest
, whatever value that is at the moment.
So in short runCoreDataFetchRequest runs a performFetch
for you.
Finally don't forget that after running a fetch, you just reload the table or collection view:
So notice
var searchLetters: String = "" {
didSet {
runCoreDataFetchRequest()
reloadData()
}
}
setting the searchLetters
in fact does the whole thing!
Upvotes: 1
Reputation: 7485
Add the below function:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if !searchText.isEmpty {
var predicate: NSPredicate = NSPredicate()
predicate = NSPredicate(format: "name contains[c] '\(searchText)'")
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Contact")
fetchRequest.predicate = predicate
do {
contacts = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch. \(error)")
}
}
tableView.reloadData()
}
Add UISearchBarDelegate and UISearchDisplayDelegate like below :
class ViewController: UIViewController, UISearchBarDelegate, UISearchDisplayDelegate
And add the searchBar delegate in viewDidLoad :
searchBar.delegate=self
Upvotes: 4