-2

I have set up a tableview that retrieves data from a firebase realtime database, and stores this data in an array ('posts') in this is the format:

(name: nil, contactEmail: Optional("35345345235"), contactPhoneNum: Optional("contact@gmail.com"), age: Optional("25"), gender: nil, lastSeen: nil, profileDescription: nil)

I want to implement a searchbar to filter the name value of the posts and return the posts which contain the searched name in the tableview, and am not sure how to do this.

Here is my code:

import UIKit
import Firebase
import FirebaseDatabase
import SwiftKeychainWrapper
import FirebaseAuth

class FeedVC: UITableViewController, UISearchBarDelegate{
    
    @IBOutlet weak var searchBar: UISearchBar!
    
    var currentUserImageUrl: String!
    var posts = [postStruct]()
    var selectedPost: Post!
    var filteredPosts = [postStruct]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        getUsersData()
        getPosts()
        searchBar.delegate = self
        // Do any additional setup after loading the view.
   //     tableView.register(PostCell.self, forCellReuseIdentifier: "PostCell")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    func getUsersData(){

        guard let userID = Auth.auth().currentUser?.uid else { return }
        Database.database().reference().child("users").child(userID).observeSingleEvent(of: .value) { (snapshot) in
            if let postDict = snapshot.value as? [String : AnyObject] {
                self.tableView.reloadData()
            }
        }
    }
    
    struct postStruct {
        let name : String!
        let contactEmail : String!
        let contactPhoneNum : String!
        let age : String!
        let gender : String!
        let lastSeen : String!
        let profileDescription : String!
    }
    
    func getPosts() {
        let databaseRef = Database.database().reference()
        databaseRef.child("firstName").queryOrderedByKey().observe( .childAdded, with: {
                    snapshot in
                    let name = (snapshot.value as? NSDictionary)!["name"] as? String
                    let contactEmail = (snapshot.value as? NSDictionary
                    )!["contactEmail"] as? String
                    let contactPhoneNum = (snapshot.value as? NSDictionary
                    )!["contactPhoneNum"] as? String
                    let age = (snapshot.value as? NSDictionary
                    )!["age"] as? String
                    let gender = (snapshot.value as? NSDictionary
                    )!["gender"] as? String
                    let lastSeen = (snapshot.value as? NSDictionary
                    )!["lastSeen"] as? String
                    let profileDescription = (snapshot.value as? NSDictionary
                    )!["profileDescription"] as? String
            self.posts.append(postStruct(name: name,contactEmail:contactEmail, contactPhoneNum:contactPhoneNum, age:age, gender:gender, lastSeen:lastSeen, profileDescription:profileDescription))
            DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                })
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print("posts count = ", filteredPosts.count)
        return filteredPosts.count

   }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
      //  tableView.dequeueReusableCell(withIdentifier: "PostCell")!.frame.size.height
        return 230
    }
   override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell") as? PostCell else { return UITableViewCell() }
    cell.nameLabel?.text = filteredPosts[indexPath.row].name
        cell.contactEmailLabel?.text = filteredPosts[indexPath.row].contactEmail
        cell.contactPhoneNumLabel?.text = filteredPosts[indexPath.row].contactPhoneNum
        cell.ageLabel?.text = filteredPosts[indexPath.row].age
        cell.genderLabel?.text = filteredPosts[indexPath.row].gender
        cell.lastSeenLabel?.text = filteredPosts[indexPath.row].lastSeen
        cell.profileDescriptionLabel?.text = filteredPosts[indexPath.row].profileDescription
        print(filteredPosts)
        return cell
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
          filteredPosts = posts.filter { $0.name?.lowercased().contains(searchText.lowercased()) == true }
}


}

maya
  • 17
  • 5
  • Some clarification is needed; Do you want to load every single post + data into memory from Firebase and store all of them in an array, then filter that? Or do you want to query firebase for data and return just that subset of data? It's important as if you have a million posts, you're going to overwhelm the device loading all of that in. Also, does your array just contain 'name' (as in a String) or does it contain the entire 'post'? – Jay Aug 16 '20 at 14:35
  • Sorry, I am pretty new to firebase. I want to search through the 'name' of each posts to return names that match, so I'm not sure which approach to take. The 'posts' array contains the entire post data stored as postStruct (format shown above). – maya Aug 16 '20 at 18:52
  • You will need to make a decision as the approach is very different. If you have a lot of posts, it can overwhelm the device as mentioned but if you're querying firebase you'll only want to return a subset of data. – Jay Aug 16 '20 at 19:46
  • This isn't really a bad question - just need some clarity if the OP is searching and array or Firebase. Voted to re-open – Jay Aug 17 '20 at 19:32
  • I would preferably be querying firebase for the names – maya Aug 18 '20 at 09:31
  • If you want to filter/query FIREBASE for the names, then all of code in the question is incorrect as it's attempting to filter an array (e.g. `posts`) stored in memory, not Firebase. If you want to load ALL OF THE DATA from Firebase into an array, then filter, the code is more on point. Also note that within getPosts, you immediatly tell the tableView to refresh itself, which will show nothing because the filteredArray contains nothing at that point. Also, as you're typing in the searchbar, it calls the filter function but does not update the table. Lastly, remove `DispatchQueue.main.async` – Jay Aug 18 '20 at 17:20
  • Oh, also note that your question states that your data is `in this format: name: nil, contactEmail:` but the object in the question has a name property that cannot be nil `let name : String!`. That's confusing. – Jay Aug 18 '20 at 17:36

1 Answers1

1

Looping over the posts will provide element of type Post not String. Here's the fix:

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    filteredPosts = []
    for post in posts {
        if post.name?.lowercased().contains(searchText.lowercased()) == true {
            filteredPosts.append(post)
        }
    }
}

Or simply use a higher-order method like filter inspired from @Jay's comment.

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    filteredPosts = posts.filter { $0.name?.lowercased().contains(searchText.lowercased()) == true }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • @maya Nothing wrong with the posted answer, other than extended loops should be avoided. This is a bit shorter `let filteredResults = posts.filter { $0.name.lowercased() == "string you're searching for".lowercased() }` – Jay Aug 17 '20 at 17:42
  • @Jay I don't even know why I added the extra loop in my answer. I made a few modifications to your suggestion like we don't need to declare a new variable`filteredResults` we could directly assign result to `filteredPosts` also contains search and optional `name` property :). – Frankenstein Aug 17 '20 at 18:43
  • @Jay I have changed my code to include this (see edit), but **filteredResults** does not have any values in it: **print(filteredPosts.count)** is returning post count = 0 – maya Aug 18 '20 at 09:37
  • @maya See the comments to the question but if you want help, you need to do more troubleshooting. For example, does the posts array actually contain any data? Is the delegate function actually being called when you type? Did you add a breakpoint and examine the vars as you type? What was the result? See - we can't do those things for you but basic troubleshooting may lead the cause and will provide more accurate info to include in the question so we have something to go on. Right now - we're just guessing. – Jay Aug 18 '20 at 17:29