1

I am trying to search an array of objects that I'm pulling from a Firebase database. The data is loading fine but when I try to search nothing shows. I've only been learning iOS/Swift development for about 6 weeks now. Below is code I have.

The app was crashing as soon as I clicked into the search bar. I managed to get the search code to stop crashing but I'm getting no returns in my search. Is this because I'm search a Firebase database? I'm loading the firebase data into an array so it should be searchable right?

import UIKit
import Firebase

class ServicesTableViewController: UITableViewController, UISearchResultsUpdating {

    var services = [Service]()
    var filteredSearch = [Service]()

    var searchController: UISearchController!
    var resultsController = UITableViewController()


    override func viewDidLoad() {
        super.viewDidLoad()


        // Turn this back on to cache data
        FIRDatabase.database().persistenceEnabled = true

        fetchServices()

        self.resultsController.tableView.dataSource = self
        self.resultsController.tableView.delegate = self

        self.searchController = UISearchController(searchResultsController: self.resultsController)
        self.tableView.tableHeaderView = searchController.searchBar
        self.searchController.searchResultsUpdater = self

        searchController.dimsBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search services..."
        searchController.searchBar.tintColor = UIColor.white
        searchController.searchBar.barTintColor = UIColor(red: 164.0/255.0, green: 45.0/255.0, blue: 9.0/255.0, alpha: 1.0)

        navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
    }

    // MARK: - Search Controller

    func filterContent(for searchText: String) {
        filteredSearch = services.filter({ (service) -> Bool in
            if let name = service.name {
                let isMatch = name.localizedCaseInsensitiveContains(searchText)
                return isMatch
            }

            return false
        })
    }

    func updateSearchResults(for searchController: UISearchController) {
        if let searchText = searchController.searchBar.text {
            filterContent(for: searchText)
            tableView.reloadData()
        }
    }

    // MARK: Database connection and data fetch

    func fetchServices () {

        FIRDatabase.database().reference().child("services").observe(.childAdded, with: { (snapshot) in

            print(snapshot)

            if let dictionary = snapshot.value as? [String: AnyObject] {

                let service = Service()
                service.setValuesForKeys(dictionary)

                self.services.append(service)


                self.tableView.reloadData()



            }

        })


    }



    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {

        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if searchController.isActive {
            return filteredSearch.count
        } else {
            return services.count
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ServicesTableViewCell


        // Check to see if the user is searching or not
        let service = (searchController.isActive) ? filteredSearch[indexPath.row] : services[indexPath.row]

//        let service = services[indexPath.row]
        cell.programLabel.text = service.name
        cell.programLabel.font = UIFont(name: "Avenir-Medium", size: 18.0)
        cell.programLabel.textColor = UIColor(red: 0.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0)
        cell.branchLabel.text = service.branch
        cell.branchLabel.font = UIFont(name: "Avenir-Medium", size: 14.0)
        cell.branchLabel.textColor = UIColor(red: 90.0/255.0, green: 86.0/255.0, blue: 92.0/255.0, alpha: 1.0)
        cell.servicesLabel.text = service.shortDesc
        cell.servicesLabel.font = UIFont(name: "Avenir-Light", size: 14.0)
        cell.servicesLabel.textColor = UIColor(red: 164.0/255.0, green: 45.0/255.0, blue: 9.0/255.0, alpha: 1.0)
        cell.thumbnailImageView.image = UIImage(named: service.imageName!)


        return cell
    }


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showServicesDetail" {
            if let indexPath = tableView.indexPathForSelectedRow {
                let destinationController = segue.destination as! ServicesDetailViewController
                destinationController.services = services[indexPath.row]
            }
        }
    }





}

Here's my current storyboard. just two views and a nav controller, enter image description here

5
  • Because services.filter will return an array of Service but filteredSearch is an array of String. Why not var filteredSearch = [Service]()? Commented Apr 3, 2017 at 6:19
  • You need to show us your Service class Commented Apr 3, 2017 at 6:22
  • Thank you so much for your suggestion. I made the change to var filteredSearch = [Service]() but I'm getting an error can't convert value of (string) -> Bool to expected argument type (Service) -> Bool. Commented Apr 3, 2017 at 6:28
  • Here's the class I made. File name "Service.swift" import UIKit class Service: NSObject { var name: String? var branch: String? var shortDesc: String? var longDesc: String? var imageName: String? var mainPhone: String? var locations: String? var hours: String? var website: String? } Commented Apr 3, 2017 at 6:29
  • I'm using the tutorial from this link. Worked great until I made the custom class. youtube.com/watch?v=XtiamBbL5QU Commented Apr 3, 2017 at 6:31

3 Answers 3

3

if your Service class is as follows

class Service: NSObject {
    var serviceID: NSInteger!
    var serviceName: String!

}

you can retrieve filter services with serviceName containing self.searchController.searchBar.text by

let fiterServices = services.filter({$0.serviceName.lowercased().range(of: self.searchController.searchBar.text!.lowercased()) != nil})

Hope this helps.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you Sree. When I tried your code I got an error "consecutive states on a line must be separated by ;" I probably did something I'm sure
@ShootLighter: i made an edit. please see the code (removed closing bracket next to nil and space between $0. serviceName)
@ShootLighter please accept the answer if it solved your issue.
0

I assumed that you want to search by name:

filteredSearch = services.filter { (service: Service) -> Bool in

    if let name = service.name,
        let searchText = self.searchController.searchBar.text?.lowercased() {
        return name.lowercased().contains(searchText)
    } else {
        return false
    }
}

Also, let change filteredSearch to an array of Service:

var filteredSearch = [Service]()

6 Comments

Thanks Danh. I tried your code and it built fine with no errors but when I clicked into the searcher to search the app crashed with a SIGABRT error. :(
Danh, Here's the error from the console. 2017-04-03 19:59 MCCSServicesGuide[64700:9332471] *** Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UITableView.m:6730 2017-04-03 19:59:19.336 MCCSServicesGuide[64700:9332471] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard' *** First throw call stack:
I think the tableView couldn't find the "Cell" identifier to dequeue. If it's possible, let show me your storyboard and code
I just edited my post and put all of the current code from the tableviewcontroller plus the custom class.
Did you forget set identifier for the ServicesTableViewCell cell in the storyboard? Select your cell in the Main.storyboard, then select Attributes inspector in the right side and set Cell in the Indentifier box.
|
0

I have an array of object(codeDataObjectList)

    import Foundation

    class CodeDataObject:NSObject{

        private var collections:[String]?
        private var code: String?
        private var shortDescription: String?
        private var longDescription: String?
        private var sectionName: String?
        private var specialtyName: String?
        private var chapterName: String?
        private var isStarred: Bool?
        private var starredListIndex:Int?
        private var type: String?
        .
        .
        .

below code can search a text in each object and you can update table data with filteredTableData

func  updateSearchResults(for searchController: UISearchController) {
        filteredTableData.removeAll(keepingCapacity: false)
        let searchPredicate = NSPredicate(format: "code CONTAINS[c] %@ OR longDescription CONTAINS[c] %@ OR shortDescription CONTAINS[c] %@", searchController.searchBar.text!, searchController.searchBar.text!, searchController.searchBar.text!)
        let array = (self.codeDataObjectList as NSArray).filtered(using: searchPredicate)
        filteredTableData = array as! [CodeDataObject]
        self.tableView.reloadData()
    }

3 Comments

I like this solution Hamed because it enables me to search multiple fields but I'm having some difficulty understanding how to implement it in my code. Sorry I'm sure it's me since I'm new. I just posted all my code for the tableviewcontroller. If you don't mind, how would I implement this code using my arrays, variables and classes. ? Sorry if this doesn't make sense and thank you all for helping me.
I managed to get the search code to stop crashing but I'm getting no returns in my search. Is this because I'm search a Firebase database? I'm loading the firebase data into an array so it should be searchable right? Is this why you were recommending using NSPredicate? Thanks in advance for any help.
excuse me, I don't know

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.