Lucian
Lucian

Reputation: 137

Search bar in a TableViewController with Core Data

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

Answers (2)

Fattie
Fattie

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

Vini App
Vini App

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

Related Questions