Search Bar Not Working

The search bar in my app isn't working. Ive narrowed down the problem to the method cellforRowAt. It's not able to show the search result when word are typed. Can someone please assist me ? Project Link: https://github.com/lexypaul13/Covid-News.git

Code Block  
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //determines what data source should be used when user types
        if isFiltering{
            return filteredArticles?.count ?? 0
    }
        return articles?.count ?? 0
    }
    
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
        let news: Articles
        filteredArticles = articles
        if isFiltering{
            news = filteredArticles![indexPath.row]
        }
        else{
            news = articles![indexPath.row]
        }
        cell.authorName.text = filteredArticles?[indexPath.row].author
        cell.headLine.text = filteredArticles?[indexPath.row].title
        cell.newsImage.downloadImage(from:(self.filteredArticles?[indexPath.item].urlImage ?? "nill"))
        cell.timePublication.text = filteredArticles?[indexPath.row].publishedAt
        if let dateString = filteredArticles?[indexPath.row].publishedAt,
        let date = indDateFormatter.date(from: dateString){
                   let formattedString = outDateFormtter.string(from: date)
                   cell.timePublication.text = formattedString
               } else {
                   cell.timePublication.text = "----------"
               }
        
               return cell
           }
   
    func updateSearchResults(for searchController: UISearchController) {
        let searchBar = searchController.searchBar
        filterContentForSearchText(searchBar.text!, articles!)
    }
    func filterContentForSearchText(_ searchText:String ,_ category: [Articles]){
        filteredArticles =  articles?.filter({ (article:Articles) -> Bool in
            return article.description.lowercased().contains(searchText.lowercased())
            
        })
        table_view.reloadData()
    }
     

 
Answered by Claude31 in 640093022
A first practical advice. When you paste code here, use "Paste and match style" to avoid all the extra blank lines that make code hard to read:

Code Block
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // determines what data source should be used when user types
if isFiltering {
return filteredArticles?.count ?? 0
}
return articles?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
let news: Articles
filteredArticles = articles
if isFiltering {
news = filteredArticles![indexPath.row]
}
else {
news = articles![indexPath.row]
}
cell.authorName.text = filteredArticles?[indexPath.row].author
cell.headLine.text = filteredArticles?[indexPath.row].title
cell.newsImage.downloadImage(from:(self.filteredArticles?[indexPath.item].urlImage ?? "nill"))
cell.timePublication.text = filteredArticles?[indexPath.row].publishedAt
if let dateString = filteredArticles?[indexPath.row].publishedAt,
let date = indDateFormatter.date(from: dateString){
let formattedString = outDateFormtter.string(from: date)
cell.timePublication.text = formattedString
} else {
cell.timePublication.text = "----------"
}
return cell
}
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!, articles!)
}
func filterContentForSearchText(_ searchText:String ,_ category: [Articles]){
filteredArticles = articles?.filter({ (article:Articles) -> Bool in
return article.description.lowercased().contains(searchText.lowercased())
})
table_view.reloadData()
}


Now, back to your code.

In comment line 1, you say:

func tableView(_ tableView: UITableView, numberOfRowsInSection…) determines what data source should be used when user types

That's not the case, I hope you don't count on this for the selection.
But effectively, isFiltering is doing so.

I don't understand the logic.
Line 11 (my numbering), you set filteredArticles to articles.
What is the insterest then of this filteredArticles ?

Shouldn't the function be like :

Code Block
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
let news: Articles
var articlesToUse = articles // by default
// Get rid of this filteredArticles = articles
if isFiltering {
articlesToUse = filteredArticles
// news = filteredArticles![indexPath.row]
} /* Remove
else {
news = articles![indexPath.row]
} */
news = articlesToUse![indexPath.row]
cell.authorName.text = articlesToUse?[indexPath.row].author // instead of filteredArticles
// The same on following lines


Question: why do you make articles an optional ?

Note: use of underscore as in table_view in discouraged in Swift.
You'd better give a meaningful name with CamelCase writing, for instance:
articlesTableView
Accepted Answer
A first practical advice. When you paste code here, use "Paste and match style" to avoid all the extra blank lines that make code hard to read:

Code Block
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // determines what data source should be used when user types
if isFiltering {
return filteredArticles?.count ?? 0
}
return articles?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
let news: Articles
filteredArticles = articles
if isFiltering {
news = filteredArticles![indexPath.row]
}
else {
news = articles![indexPath.row]
}
cell.authorName.text = filteredArticles?[indexPath.row].author
cell.headLine.text = filteredArticles?[indexPath.row].title
cell.newsImage.downloadImage(from:(self.filteredArticles?[indexPath.item].urlImage ?? "nill"))
cell.timePublication.text = filteredArticles?[indexPath.row].publishedAt
if let dateString = filteredArticles?[indexPath.row].publishedAt,
let date = indDateFormatter.date(from: dateString){
let formattedString = outDateFormtter.string(from: date)
cell.timePublication.text = formattedString
} else {
cell.timePublication.text = "----------"
}
return cell
}
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!, articles!)
}
func filterContentForSearchText(_ searchText:String ,_ category: [Articles]){
filteredArticles = articles?.filter({ (article:Articles) -> Bool in
return article.description.lowercased().contains(searchText.lowercased())
})
table_view.reloadData()
}


Now, back to your code.

In comment line 1, you say:

func tableView(_ tableView: UITableView, numberOfRowsInSection…) determines what data source should be used when user types

That's not the case, I hope you don't count on this for the selection.
But effectively, isFiltering is doing so.

I don't understand the logic.
Line 11 (my numbering), you set filteredArticles to articles.
What is the insterest then of this filteredArticles ?

Shouldn't the function be like :

Code Block
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
let news: Articles
var articlesToUse = articles // by default
// Get rid of this filteredArticles = articles
if isFiltering {
articlesToUse = filteredArticles
// news = filteredArticles![indexPath.row]
} /* Remove
else {
news = articles![indexPath.row]
} */
news = articlesToUse![indexPath.row]
cell.authorName.text = articlesToUse?[indexPath.row].author // instead of filteredArticles
// The same on following lines


Question: why do you make articles an optional ?

Note: use of underscore as in table_view in discouraged in Swift.
You'd better give a meaningful name with CamelCase writing, for instance:
articlesTableView
There are some other errors probably:

news is never used. Why do you declare it ?

You use row somewhere and item in other place ?
The error is in fact on line 40:
Code Block
return article.description.lowercased().contains(searchText.lowercased())

you should not filter on description, but on content.

if you change with
Code Block
return article.title!.lowercased().contains(searchText.lowercased())

Then you get the correct selection.

Note : there is a remaining error: image is not corrsponding to selected article

You have to replace line 20 (of my counting)
Code Block
cell.newsImage.downloadImage(from:(self.filteredArticles?[indexPath.item].urlImage ?? "nill"))

by
Code Block
        cell.newsImage.downloadImage(from:(articlesToUse?[indexPath.row].urlImage ?? "nill"))

Now it works perfectly correct.

The full cellForRowAt func code is:

Code Block
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! NewsTableViewCell
let news: Articles
var articlesToUse = articles // by default
if isFiltering {
articlesToUse = filteredArticles
}
news = articlesToUse![indexPath.row]
cell.authorName.text = news.author
cell.headLine.text = news.title
cell.newsImage.downloadImage(from:(articlesToUse?[indexPath.row].urlImage ?? "nill"))
cell.timePublication.text = news.publishedAt
if let dateString = articles?[indexPath.row].publishedAt,
let date = indDateFormatter.date(from: dateString){
let formattedString = outDateFormtter.string(from: date)
cell.timePublication.text = formattedString
} else {
cell.timePublication.text = "----------"
}
return cell
}

@Claude31 Thank you for the help. Ive been able to spot the bugs. Can you please recommend a tutorial that provides a comprehensive description on how to debug ?
Great if it works now.
Here is a tutorial that may help: https ://www.hackingwithswift .com/read/18/overview

Debug is a question of method.
I can explain how I did with your code.
  1. Just read the code carefully. And notice the warnings as "news is never used" You will find a lot of issues.

  2. When the table didn't show anything, I added print statements in several places to monitor the count of articles and filteredArticles. I noticed that filteredArticles was always empty.

  3. Then, the hard part: you need to find out why ? It was likely that filteredArticles did not find any. So, needed to look at:

Code Block
func filterContentForSearchText(_ searchText:String ,_ category: [Articles]){
filteredArticles = articles?.filter({ (article:Articles) -> Bool in
return article.description.lowercased().contains(searchText.lowercased())
})
table_view.reloadData()
}


4. For testing, I added a print(article.description) and got 50 lines as:
<covid_news.articles: 0x600003f67e40>
So, it was clear that was the the content we wanted to look at.

5. Thus, I looked at the Articles and decided to replace description by author!.
I tested and saw it worked when I typed Kim for instance.
Looking further, I found that the content was in title, so I replaced description by title!.
And it worked smoothly.

6. Testing further showed that images were no more matching articles after filter. That was because I did not use the right array in:
cell.newsImage.downloadImage(from:(articlesToUse?[indexPath.row].urlImage ?? "nill"))

So, as conclusion, debug is often a zooming in exercise. Guess and find where the problem could come from (which area), add a lot of print statement to localize precisely and then find the error and correct.
Search Bar Not Working
 
 
Q