1

I am new to Xcode and Swift but the understanding is coming. The problem right now is that I have a table view with a searchview in it. When I run the code, data is received from the api to the tableview correctly but when I try to make a search then its returns an empty list.

How can I correct my code?

My Controller:

 var clientDetails = [Client]() var currentClientDetails = [Client]() func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return currentClientDetails.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "clients_list") as? ClientCellTableViewCell else { return UITableViewCell() } cell.nameLb.text = "Name: " + currentClientDetails[indexPath.row].CLNT_FULLNAME cell.emailLb.text = "Email: " + currentClientDetails[indexPath.row].CLNT_EMAIL cell.phonenumberLb.text = "Phone Number: " + currentClientDetails[indexPath.row].CLNT_PHONE cell.clientidLb.text = "ID: " + currentClientDetails[indexPath.row].CLNT_CODE return cell } override func viewDidLoad() { super.viewDidLoad() self.clientsTableView.delegate = self self.clientsTableView.dataSource = self fetchData() setUpSearchBar() alterLayout() } func alterLayout() { clientsTableView.tableHeaderView = UIView() // search bar in section header clientsTableView.estimatedSectionHeaderHeight = 50 // search bar in navigation bar //navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchBar) navigationItem.titleView = searchBar searchBar.showsScopeBar = false // you can show/hide this dependant on your layout searchBar.placeholder = "Search Client by Name" } private func setUpSearchBar() { searchBar.delegate = self } func fetchData(){ let myapiurl = URL(string: "https://fpay.com/api") guard let downloadURL = myapiurl else { return } URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in guard let data = data, error == nil, urlResponse != nil else { DispatchQueue.main.async { let alert = UIAlertController(title: "Fofoofo Pay", message: "Please try again or check your internet connection", preferredStyle: .alert) let action = UIAlertAction(title: "Ok", style: .default, handler: nil) alert.addAction(action) self.present(alert, animated: true, completion: nil) } return } print("JSON downloaded") do { let decoder = JSONDecoder() let downloadedJson = try decoder.decode(Client_details.self, from: data) self.clientDetails = downloadedJson.client_details self.currentClientDetails = self.clientDetails DispatchQueue.main.async { self.clientsTableView.reloadData() } } catch { print(error) } }.resume() } // Search Bar func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { currentClientDetails = clientDetails.filter({ client -> Bool in switch searchBar.selectedScopeButtonIndex { case 0: if searchText.isEmpty { return true } return client.CLNT_FULLNAME.lowercased().contains(searchText.lowercased()) default: return false } }) clientsTableView.reloadData() } 

Client class:

 Client_details: Codable { let client_details: [Client] init(client_details: [Client]) { self.client_details = client_details } } class Client:Codable { let CLNT_CODE:String let CLNT_FULLNAME:String let CLNT_PHONE:String let CLNT_EMAIL:String init(CLNT_CODE:String, CLNT_FULLNAME:String, CLNT_PHONE:String, CLNT_EMAIL:String) { self.CLNT_CODE = CLNT_CODE self.CLNT_FULLNAME = CLNT_FULLNAME self.CLNT_PHONE = CLNT_PHONE self.CLNT_EMAIL = CLNT_EMAIL } 
8
  • Do you want this to search case-sensitive, case-insensitive, partial match, match beginning, or match ending? From what I can tell right now you're attempting to filter your clientDetails and store the result in currentClientDetails however there is no comparison taking place to create a proper return value. Commented Apr 6, 2020 at 19:40
  • @xTwisteDx yes please i wish to have search case-sensitive, case-insensitive, partial match, match beginning, and match ending. Im new on swift. I dont know how to go by it. Commented Apr 6, 2020 at 19:42
  • If you put a breakpoint inside the case 0: section, is it getting to the if searchTxt.isEmpty line? Also, if you put a break point at the end of the function at clientsTableView.reloadData(), what does currentClientDetails look like? Commented Apr 6, 2020 at 19:52
  • 2
    currentClientDetails = clientDetails.filter({ $0.contains(searchText.lowercased()}) Commented Apr 6, 2020 at 20:01
  • @xTwisteDx thank you but look at my question, ive updated it. I honestly dont know what to do with what you just sent. With all humility Sir Commented Apr 6, 2020 at 20:05

2 Answers 2

1
+100

I wanted to take an opportunity to give you a breakdown of what's happening so that you can continue to learn and bolster your skills. So the issue that you're having is that your searchBar(_ textDidChange:...) method is not filtering the way you're expecting. Particularly this block of code.

currentClientDetails = clientDetails.filter({ client -> Bool in switch searchBar.selectedScopeButtonIndex { case 0: if searchText.isEmpty { return true } return client.CLNT_FULLNAME.lowercased().contains(searchText.lowercased()) default: return false } }) 

There are two possible reasons this is not producing anything. Reason #1, your client.CLNT_FULLNAME is returning "" or your method is always returning false because the first element is "" or hitting default: which returns false.

Based on the code that you've provided client is never sequenced. Meaning it's always the same thing every single time the searchBar(_ textDidChange:...) method is hit, never changes inside of your closure. So to fix this let's break down the solution.

Solution

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { currentClientDetails = clientDetails.filter({ $0.CLNT_FULLNAME.contains(searchText) }) clientsTableView.reloadData() } 

So, what's happening inside of this closure vs what you have? What exactly does .filter() do, and how does that closure work? There are two versions of .filter() and in your case, you're using a sequential closure. That means that it will return an [object] based on a given predicate (Formula).

What is the predicate given here and what's that fancy $0 all about? The predicate given in the .filter() is checking each element in your [object] and returning true or false for each element. True if it contains some or all of a match, or false if not. Once the closure completes, or the sequence completes, it returns a given object. In your case, it appears to be a [client].

I like to consider the .filter() method as a sort of for _ in 0..X loop and this is an important concept to consider when you are using those aforementioned $0 fancy-looking things. Essentially that $0 is saying the first element in the sequence compared to what? or client[0] compared to what. That would be the initial sequence and it will continue until the sequence is completed, aka the count of the for _ in 0...X.count.

I hope that this gives you something to consider and allows you to continue coding. Thank you for posting a thoughtful question and being actively engaged while working through the problem.

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

1 Comment

Awesome halfer you just made my day! Thanks so so much!
1

This is fixed. I changed the code in my searchBar function to this:

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { currentClientDetails = clientDetails if searchText.isEmpty == false { currentClientDetails = clientDetails.filter({ $0.CLNT_FULLNAME.contains(searchText) }) } clientsTableView.reloadData() } 

It now returns only the ones searched- It is case-sensitive, case-insensitive, partial match, match beginning, and match ending. Also when search field is empty it shows all the data.

4 Comments

That's literally the same thing that my solution provided... currentClientDetails = clientDetails.filter({ $0.contains(searchText.lowercased()}) except that mine will check any matches, yours will only check the leading matches.
i get error at contains when i use it. is there a solution? @xTwisteDx
I think I have my {} in the wrong spot. I don't have XCode available at the moment. Try removing the ().
currentClientDetails = clientDetails.filter({ $0.CLNT_FULLNAME.contains(searchText) }) Thank you for the inspiration @xTwisteDx. My problem is totally solved

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.