UITableview loads data from Firebase and duplicate rows - ios

I am trying to build an app that is loading data from firebase-database.
Saving members to Firebase is working without any problems. Load members from Firebase to my UITableView is working then I am sorting the members and add respectively member under sections header according to first names first letter(A, B, C, etc as seen in iOS contact app) and this is working as well however my problem occur after I have loaded all my users and for example go to Tab 1 and then switch back to Members Tab all displayed members/cells are duplicated. If I repeat the same procedure switching tabs back and forth all cells triplicate and it goes on.
I have searched different sources for a solution but I can not find anything that is similar.
Does anyone know a solution or what I an doing wrong?
Thanks!
My Viewcontroller:
import Foundation
import UIKit
class MembersTableViewController: UITableViewController {
var FBref = FIRDatabaseReference()
var members: [Member] = []
var membersDict = [String: [String]]()
var memberSectionTitles = [String]()
// TODO: Implement user.
//var user: AdminUser!
let fakeuservariable = "fakeuser"
#IBOutlet var memberListTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
loadDataFromFirebase()
createFirstnameDict()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return memberSectionTitles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let firstLetterKey = memberSectionTitles[section]
if let firstnameValues = membersDict[firstLetterKey] {
return firstnameValues.count
}
return 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return memberSectionTitles[section]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "membercell", for: indexPath)
let firstLetterKey = memberSectionTitles[indexPath.section]
if let firstnameValues = membersDict[firstLetterKey] {
cell.textLabel?.text = firstnameValues[indexPath.row]
// Not working
//let memberDetails = members[indexPath.row]
//cell.detailTextLabel!.text = "Amount left: \(memberDetails.memberamount)"
}
return cell
}
func createFirstnameDict() {
for firstname in members {
var firstLetter = firstname.firstname
let firstnameKey = firstLetter.substring(to: firstLetter.characters.index(firstLetter.startIndex, offsetBy: 1))
if var memberValues = membersDict[firstnameKey] {
memberValues.append(firstLetter)
membersDict[firstnameKey] = memberValues
} else {
membersDict[firstnameKey] = [firstLetter]
}
}
memberSectionTitles = [String](membersDict.keys)
memberSectionTitles = memberSectionTitles.sorted { $0 < $1 }
}
func loadDataFromFirebase() {
let FBref = FIRDatabase.database().reference()
FBref.child("member-list").observeSingleEvent(of: .value, with: { (snapshot) in
var resultItem: [Member] = []
for item in snapshot.children {
let memberItem = Member(snapshot: item as! FIRDataSnapshot)
resultItem.append(memberItem)
}
self.members = resultItem
self.createFirstnameDict()
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
}
My Member model:
import Foundation
struct Member {
let firstname: String
let lastname: String
let email: String
let phonenumber: String
let socialsecuritynr: String
let memberamount: String
let addedByUser: String
let key: String
let ref: FIRDatabaseReference?
init(firstname: String, lastname: String, email: String, phonenumber: String, socialsecuritynr: String, memberamount: String, addedByUser: String, key: String = "") {
self.key = key
self.firstname = firstname
self.lastname = lastname
self.email = email
self.phonenumber = phonenumber
self.socialsecuritynr = socialsecuritynr
self.memberamount = memberamount
self.addedByUser = addedByUser
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
firstname = snapshotValue["firstname"] as! String
lastname = snapshotValue["lastname"] as! String
email = snapshotValue["email"] as! String
phonenumber = snapshotValue["phonenumber"] as! String
socialsecuritynr = snapshotValue["socialsecuritynr"] as! String
memberamount = snapshotValue["memberamount"] as! String
addedByUser = snapshotValue["addedByUser"] as! String
ref = snapshot.ref
}
func toAnyObject() -> Any {
return ["firstname": firstname, "lastname": lastname, "email": email, "phonenumber": phonenumber, "socialsecuritynr": socialsecuritynr, "memberamount":memberamount, "addedByUser": addedByUser]
}
}
This is my TableView before and after:

The issue is arising from the placement of your methods that load the data which are wrongly in viewDidAppear:
loadDataFromFirebase()
createFirstnameDict()
This means that each time your view appears your data is loaded again and again. To fix the problem move these methods into viewDidLoad and you wont get the duplication issues. So you should now have:
override func viewDidLoad() {
super.viewDidLoad()
loadDataFromFirebase()
createFirstnameDict()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}

What I recommend you, is that you clear all your arrays that you populate on your before event listener. This way you make sure that when it come back from another view it will not have old data. Something like this:
self.members.removeAll()

You are displaying the data from membersDict in your cell.textlabel .
Each time your view(when you switch tabs) loads, it calls loadDataFromFirebase() .
Here, all the values get loaded again and get appended to your membersValues which you then store in membersDict.
A new instance of membersDict will not get created since you are not declaring them inside of viewDidLoad(). You have declared them inside the class but outside any function.
What append does is add an element at the end of the array. It does not overwrite the element. So if you have an array with two names, appending a name will make that your third name and not overwrite any existing names.
Each time you load the view, you are appending the names to an array that already consists of the names. This is what is causing the duplication.
Try printing the value of your membersDict or membersValues, to check if you are duplicating.
You can solve this by declaring an instance of membersDict locally such that an empty variable is created each time and use that to display data.
Hope this helps.

From my understanding and experience, you load firebase data in
override func viewDidLoad() {
super.viewDidLoad()
loadFirebaseData()
}
Your newly created data from any other view controller will appear on your table when you return to it, because your observers are still listening unless you have told them to stop listening when moving to other views.
Therefore, anytime new data appears in Firebase, your table will automatically display it.

Related

I tried to retrieving data from firebase database to tableview but I just got one element

I tried to retrieving data from Firebase database to tableview in Xcode
but I just got one element even if I have a lot of element in the database.
I followed a tutorial, I put return sonsList.count to numberOfRowsInSection as suppose but nothing happen.
Here is my code:
import UIKit
import Firebase
import FirebaseDatabase
class sons {
let name : String!
//let place : String!
init(title_String : String!){
self.name = title_String
// self.place = place_String
}
}
class sonsTableViewController: UITableViewController {
var ref:DatabaseReference!
//var sons = [String]()
var newSon: String = ""
let cellId = "cellId"
var refHandel : uint!
var sonsList = [sons]()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
let value = snapshot.value as? NSDictionary
let name = value!["name"] as! String
self.sonsList.append(sons(title_String : name))
self.tableView.reloadData()
})
//fetchName()
}
func fetchName() {
}
#IBAction func cancel(segue:UIStoryboardSegue) {
}
#IBAction func done(segue:UIStoryboardSegue) {
var sonDetailVC = segue.source as! addSonViewController
newSon = sonDetailVC.name
// sons.append(newSon)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sonsList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let label = cell?.viewWithTag(1) as! UILabel
label.text = sonsList[indexPath.row].name
return cell!
}
}
You have issues in your Database query.
You append only one value in sonsList.
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
//Parse snapshot value correctly it is array or not.
if let dicValue = snapshot.value as? [String : Any] {
for (key,value) in dicValue {
let name = value["name"] as? String
self.sonsList.append(sons(title_String : name))
}
self.tableView.reloadData()
}
})
Please refer this link for Get data in firebase Database.
https://firebase.google.com/docs/database/ios/read-and-write

Not able to save data from Firebase database to an Array

*I'm fairly new to swift
I'm currently using Swift 4, Xcode 9, and Firebase. My goal is to create an app that stores data in a list, displays it in a table view, and allows the user to add more data to the list. I'm stuck on the displaying data part, I created a function that is supposed to get the data from the database, then add it into an array so that I can display individual parts of it on a custom table view cell. Here's my code:
class OrdersPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return orders.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "orderCell", for: indexPath) as! OrderCell
cell.setOrder(order: orders[indexPath.row])
print("Adding new cell")
return cell
}
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var orders = [Order]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.ref = Database.database().reference()
orders = getOrders()
}
func getOrders() -> [Order] {
var tempArray = [Order]()
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
tempArray.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
}
return tempArray
}
Based off of my testing, the issue is that the orders array doesn't contain anything when the numberOfRowsInSection is called so it doesn't create any cells in the table view. I'm not sure why it's not working as it should and have been stuck on this for quite some time now, any help is appreciated.
getOrders() is Asynchronous call so you need to reload your table after you got data from server.
Here is the way you can achieve that.
Replace:
func getOrders() -> [Order]
with
func getOrders()
And your getOrders method will look like:
func getOrders() {
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
//Add your data into array
self.orders.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
//Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
I have updated inner code. Check comments.
Now in your viewDidLoad method Replace:
orders = getOrders()
With
getOrders()
You can use didSet during define your variable of self.orders for reloading UITableView
Here your table will automatically reload when any data is assigned to self.orders
Replace your declaration
var orders = [Order]()
with below code
var orders : [Order] = [] {
didSet {
tableView.reloadData()
}
}

Read data from firebase and populate TableViewCell

Hello I have a tableviewcell where i can populate it with custom data from my pc, but i can't use my firebase data on the cell that i have made. I want to fill my cell with String and Int, not only Strings. My code is:
PlacesTableViewController Class
import UIKit
import FirebaseDatabase
class PlacesTableViewController: UITableViewController {
//MARK: Properties
#IBOutlet weak var placesTableView: UITableView!
//database reference
var dbRef:FIRDatabaseReference?
var places = [Places]()
var myList:[String] = []
//handler
var handle:FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
dbRef = FIRDatabase.database().reference()
// Loads data to cell.
loadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
//return myList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "PlacesTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlacesTableViewCell else {
fatalError("The dequeued cell is not an instance of PlacesTableView Cell.")
}
let place = places[indexPath.row]
cell.placeLabel.text = place.name
cell.ratingControl.rating = place.rating
//cell.placeLabel.text = myList[indexPath.row]
//cell.ratingControl.rating = myRatings[indexPath.row]
return cell
}
//MARK: Private Methods
private func loadData() {
handle = dbRef?.child("placeLabel").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
print (item)
}
})
/* handle = dbRef?.child("rating").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
}
})*/
/*guard let place1 = Places(name: "Veranda", rating: 4) else {
fatalError("Unable to instantiate place1")
}
places += [place1]*/
}
}
Places Class
import UIKit
class Places {
//MARK: Properties
var name: String
var rating: Int
//MARK:Types
struct PropertyKey {
static let name = "name"
static let rating = "rating"
}
//MARK: Initialization
init?(name: String, rating: Int) {
// Initialize stored properties.
self.name = name
self.rating = rating
// Initialization should fail if there is no name or if the rating is negative.
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
}
}
PlacesTableViewCell Class
import UIKit
import FirebaseDatabase
class PlacesTableViewCell: UITableViewCell, UITableViewDelegate {
//MARK: Properties
#IBOutlet weak var placeLabel: UILabel!
#IBOutlet weak var ratingControl: RatingControl!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Firebase Database
Assuming your database layout should instead look like this (see comments above):
...
placeLabel
|
-- XXY: "Veranda"
-- YYY: "Dio Con Dio"
rating
|
-- XXX: 4
-- YYY: 1
...
then try this:
private func loadData() {
dbRef!.child("placeLabel").observe(.childAdded) {
(snapshot) in
let label = snapshot.value as! String
self.updatePlace(snapshot.key, label: label)
}
dbRef!.child("rating").observe(.childAdded) {
(snapshot) in
let rating = snapshot.value as! Int
self.updatePlace(snapshot.key, rating: rating)
}
}
private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()
private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil) {
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}
By the way, you can temporarily hack your database — using Firebase (nice!) web console — if you want to quickly validate the above solution.
Writing to Database. Try the following code to write the nodes in your database (i.e., this code reuses the same key across all place properties):
let key = dbRef!.child("placeLabel").childByAutoId().key
dbRef!.child("placeLabel").child(key).setValue(placeLab‌​el.text)
dbRef!.child("comment").child(key).setValue(commentText‌​Field.text)
dbRef!.child("rating").child(key).setValue(ratingContro‌​l.rating)
Hacking the Database. To edit the database manually, try:
open http://console.firebase.google.com
select your app
open database option
add a new node with the right key
delete the old node

Firebase Clients App

I was making an app for one of my family members so that they could better manage their clients but ran into some issues. This is my first time using Firebase and I just can't seem to get my code to work! The part in which I am getting stuck involves Firebase's Realtime Database, and I am working in XCode 8.3 with Swift 3.1.
Code:
import UIKit
import FirebaseCore
import FirebaseDatabase
import FirebaseAuth
var specClientId = ""
class MyCell: UITableViewCell {
#IBOutlet var nameCell: UILabel!
#IBOutlet var statusCell: UILabel!
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var ref: FIRDatabaseReference!
var tableArray: [String] = []
var clientId: [String] = []
var statusArray:[String] = []
#IBAction func signOut(_ sender: Any) {
UserDefaults.resetStandardUserDefaults()
performSegue(withIdentifier: "segueBackLogin", sender: self)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableArray.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellFront") as! MyCell
cell.nameCell.text = tableArray[indexPath.row]
cell.statusCell.text = statusArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
specClientId = clientId[indexPath.row]
ref.child("Users").child(specClientId).child("lastUpdate").removeValue()
performSegue(withIdentifier: "segue", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
if FIRApp.defaultApp() == nil {
FIRApp.configure()
}
ref = FIRDatabase.database().reference()
ref.child("Users").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let specificValues = value?.allKeys
self.tableArray.removeAll()
self.statusArray.removeAll()
self.clientId.removeAll()
var it = 0
for Uservalue in specificValues! {
self.tableArray.append("")
self.statusArray.append("")
self.clientId.append(Uservalue as! String)
self.ref.child("Users")
.child(Uservalue as! String)
.child("name")
.observeSingleEvent(of: .value, with: { (snapshot) in
let nameValue = snapshot.value as? String
self.tableArray.insert(nameValue!, at: it)
self.tableArray = self.tableArray.filter {$0 != ""}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
self.ref.child("Users")
.child(Uservalue as! String)
.child("lastUpdate")
.observeSingleEvent(of: .value, with: { (snapshot) in
if let nameValue = snapshot.value as? String {
self.statusArray.insert("*", at: it)
self.tableView.reloadData()
} else {
self.statusArray.insert("", at: it)
self.tableView.reloadData()
}
}) { (error) in
print(error.localizedDescription)
}
it += 1
}
}) { (error) in
print(error.localizedDescription)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My main issue is that when I get the user's name and their lastUpdate status, the array lists do not match up correctly and the TableView displays the wrong information in terms of which User has submitted their updates. To fix this issue, I tried to use the insert method in my arrays but now the app crashes. Previously, I was using the append method but that leads to the wrong information being displayed in the TableView. I would appreciate it if any of you could help me with this issue.
Note: The App Crashes due to the StatusArray not having the same amount of elements as the TableArray. This is caused by the TableArray having some empty elements with no names in them.
Thanks,
KPS
Edit 1:
for Uservalue in specificValues! {
self.clientId.append(Uservalue as! String)
let user = User()
self.ref.child("Users")
.child(Uservalue as! String)
.observeSingleEvent(of: .value, with: { (snapshot) in
let nameValue = snapshot.value as? NSDictionary
let specNameValue = nameValue?.allKeys
var i = 0
while i < specNameValue!.count {
if specNameValue?[i] as? String == "name" {
user.name = nameValue?.allValues[i] as! String
} else if specNameValue?[i] as? String == "lastUpdate" {
user.status = "*"
} else if specNameValue?[i] as? String != "name" && specNameValue?[i] as? String != "lastUpdate" && specNameValue?[i] as? String != "message" && specNameValue?[i] as? String != "adminMessage" && specNameValue?[i] as? String != "photoURL" {
user.status = ""
}
i += 1
}
}) { (error) in
print(error.localizedDescription)
}
self.tableArray.append(user)
self.tableView.reloadData()
}
The main reason your app is crashing is because in your cell for row you are reloading after loading the first user and the cell expects the statusArray to have elements already.
cell.nameCell.text = tableArray[indexPath.row]
cell.statusCell.text = statusArray[indexPath.row] // fails here I assume
There a few issues going on here that I'll try to address.
You are reloading the table immediately for each child that is iterated through. It would be smart to append elements to each array then once completed display all elements by calling tableView.reloadData()
Are status' independent of the name that you are expecting? If the data is correlated, it would be smart to create a simple Object to house this data and have a single array of data that the tableView will use for it's dataSource
Once your data is fully loaded, you could sort the data accordingly then reload the datasource to solve the issue of pulling data from the server that is out of order. This is why the append(element: ) is simple and useful
Hopefully this helps! It may seem like a bit more work but it would definitely be beneficial to performance, organization and readability for yourself.

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()
}
}
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?)

Resources