Lance Samaria
Lance Samaria

Reputation: 19592

Firebase and UISearchbarController searching via the server and not the client -Swift iOS

Does anyone have any info on how to incorporate Firebase into a UISearchController delegate? I can't find any solid info on it. There may possibly be thousands of employees.

I know how to use the search controller delegates updateSearchResultsForSearchController and using a NSPredicate to filter what I'm looking for if I was using NSUserDefaults but using Firebase I'm uncertain.

I've added some more code to my question

I have a custom data model object saved in FirebaseDatabase and I'd like to search on all of the following properties within the object.

lastName
idNumber
deptNumber
position

Searching any of these properties should first show a partial string inside the table cells until the entire string i'm looking for is shown. So if I typed in the letter "S" then all employee last names beginning with "S" should show. If I enter "Sa" the in would filter to those letters". From my understanding I should use "\u{f8ff}" to get the partial search string but no data is returned.

Anyhow here's all the code

My object is:

class Employee{
var firstName: String?
var lastName: String?
var idNumber: String?
var deptNumber: String?
var position: String?
}

My paths

-root
  -users
    -uid
      |_"email":"emailAddress"
      |_"userID":"uid"
      |_"firstName":"firstName"
      |_"lastName":"lastName"
  -employees
    -hireDate
      -uid //this is the same uid from the users node so I know who's who
        |_"firstName":"firstName"
        |_"lastName":"lastName"
        |_"idNum":"idNumber"
        |_"deptNumber":"deptNumber"
        |_"position":"position"

My rules:

What's happening here is the day an employee is hired they are asked to create a company account using their email address and pw. At the same time a "employees" path is created with a child being a "hireDate" path and finally the employees "uid" path. This employee "uid" is the path I want to search on from the "employees" node

{
  "rules": {
      "users" : {
          "$uid" : {
              ".read": true,
              ".write": "auth != null && auth.uid == $uid"
          }
      },
      "employees": {
          "$hireDate": {
              "$uid": {
                 ".read": true,
                 ".indexOn": ["lastName", "idNumber", "deptNumber", "position"]
              }
          }
     }
  }
}

My searchController

import UIKit

class SearchController: UIViewController{

@IBOutlet var tableView: UITableView!

var searchController: UISearchController!
var employeesToFilter = [Employee]()
var filteredSearchResults = [Employee]()


override func viewDidLoad() {
    super.viewDidLoad()
    self.searchController = UISearchController(searchResultsController: nil)

    self.tableView.delegate = self
    //all searchController properties get set here no need to include them though

    let ref = FIRDatabase.database().reference()
    let employeeRef = ref.child("employees")

    employeeRef?.queryOrderedByChild("lastName").queryStartingAtValue("\u{f8ff}").queryLimitedToFirst(20).observeEventType(.ChildAdded, withBlock: {

        (snapshot) in

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

            let firstName = dict["firstName"] as! String
            let lastName = dict["lastName"] as! String
            let idNumber = dict["idNumber"] as! String
            let deptNumber = dict["deptNumber"] as! String
            let position = dict["position"] as! String

            let employee = Employee()
            employee.firstName = firstName
            employee.lastName = lastName
            employee.idNumber = idNumber
            employee.deptNumber = deptNumber
            employee.position = position

            self.employeesToFilter.append(employee)
        }
    })
    self.tableView.reloadData()
}

override func viewDidAppear(animated: Bool) {
    self.searchController.active = true
}

deinit {
    self.searchController = nil
}

}


//MARK:- TableView Datasource
extension SearchBuildingController: UITableViewDataSource, UITableViewDelegate{

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.filteredSearchResults.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("SearchCell", forIndexPath: indexPath) as! SearchCell

    let searchString = self.filteredSearchResults[indexPath.row]

    cell.firstNameLabel.text = searchString.firstName
    cell.lastNameLabel.text = searchString.lastName
    cell.idNumberLabel.text = searchString.idNumber
    cell.deptNumberLabel.text = searchString.deptNumber
    cell.positionLabel.text = searchString.position
    return cell
}
}

//MARK:- SearchController Delegates
extension SearchController: UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate{

func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
    tableView.reloadData()
}

func updateSearchResultsForSearchController(searchController: UISearchController) {

    self.employeesToFilter.removeAll(keepCapacity: false)
    self.filteredSearchResults.removeAll(keepCapacity: false)

    let searchText = self.searchController.searchBar.text

    let searchPredicate = NSPredicate(format: SELF.lastName CONTAINS [c] %@ OR SELF.idNumber CONTAINS [c] %@ OR SELF.deptNumber CONTAINS[c] %@ OR SELF.position CONTAINS [c] %@", searchText!, searchText!, searchText!, searchText!)

    let array = (self.employeesToFilter as NSArray).filteredArrayUsingPredicate(searchPredicate)
    self.filteredSearchResults = array as! [Employee]

    tableView.reloadData()
}
}

Upvotes: 0

Views: 1262

Answers (1)

Donny
Donny

Reputation: 76

Here is an example of how I have accomplished this using Firebase building a list of campuses. This method loads all of the data that is in the table view up front making it easy to search and filter.

My campus object is pretty simple with an id and a name.

struct Campus {
    var id: String
    var name: String
}

In the view controller I have two arrays. One is to hold the list of all campuses returned and the other array is for the filtered campuses.

let campuses = [Campus]()
let filteredCampuses = [Campus]()

I then called a method that I had set up to load the campuses from Firebase.

override func viewDidLoad() {
    ...

    getAllCampusesFromFirebase() { (campuses) in
        self.campuses = campuses
        dispatch_async(dispatch_get_main_queue(), {
            self.tableView.reloadData()
        })
    }
}

Then when performing the search I filter out the campuses comparing the campus name to the search text from the search bar.

func updateSearchResultsForSearchController(searchController: UISearchController) {
    guard let searchText = searchController.searchBar.text else {
        return
    }

    filteredCampuses = campuses.filter { campus in
        return campus.name.lowercaseString.containsString(searchText.lowercaseString)
    }

    tableView.reloadData()
}

If you are not loading all of the data up front then Firebase provides some handy methods to call that you can use to filter the data based on the reference path. https://firebase.google.com/docs/database/ios/lists-of-data

queryStarting(atValue) or queryStarting(atValue:childKey:) would probably be the one that you'd want to use in this case.

ref.queryStarting(atValue: Any?)
ref.queryStarting(atValue: Any?, childKey: String?)

Upvotes: 2

Related Questions