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