SearchController filtering problem in TableView - ios

I've next model:
struct HashTags {
var title = ""
var tags = [String]()
}
and I try use UISearchController for seach text in tags array.
I impelemented UISearchController in my TableViewController:
class TagsController: UITableViewController {
var hashtags = [HashTags]()
var filtered = [HashTags]()
let searchController = UISearchController(searchResultsController: nil)
var searchBarIsEmpty: Bool {
guard let text = searchController.searchBar.text else { return false }
return text.isEmpty
}
var isFiltering: Bool {
return searchController.isActive && !searchBarIsEmpty
}
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
navigationItem.searchController = searchController
definesPresentationContext = true
}
override func numberOfSections(in tableView: UITableView) -> Int {
if isFiltering {
return filtered.count
}
return hashtags.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if isFiltering {
return filtered[section].title
}
return hashtags[section].title
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filtered[section].tags.count
}
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Tags", for: indexPath)
var item = HashTags()
if isFiltering {
item = filtered[indexPath.section]
} else {
item = hashtags[indexPath.section]
}
let tags = item.tags
cell.textLabel?.text = tags.joined(separator: ", ")
return cell
}
}
extension TagsController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
func filterContentForSearchText(_ searchText: String) {
filtered = hashtags.filter({ (hashtag: HashTags) -> Bool in
return hashtag.tags.contains(searchText.lowercased())
})
tableView.reloadData()
}
}
But it doesn't work as it should and when I start type text I receive blank screen. I need UISearchController work correctctly and search for text in HashTags tags arrays, showing correct number of sections and rows.

Related

searchResults not showing correct words

I have a searchResultsViewController in my iOS application that displays an array of data for the user to be able to search through. When I try to search a random letter, lets say P for instance it does not show any of the words containing P.
the code that I used to create this searchResults is,
var array = ["Assembly", "Auto Care", "Electronic Help", "Item Delivery", "Handyman", "House Chores", "Junk Removal", "Lawn & Yard Care", "Moving", "Painting", "Pet Care", "Seasonal Work"]
var selectedItems = [String]()
var searchController = UISearchController()
var filteredArray = [String]()
var resultsController = UITableViewController()
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: resultsController)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
searchController.searchBar.showsCancelButton = true
searchController.searchBar.showsScopeBar = true
searchController.searchBar.delegate = self
let attributes = [NSAttributedString.Key.foregroundColor: GREEN_Theme]
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(attributes, for: UIControl.State.normal)
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Done"
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
done()
}
func updateSearchResults(for searchController: UISearchController) {
filteredArray = array.filter({ (array:String) -> Bool in
if array.contains(searchController.searchBar.text!) {
return true
} else {
return false
}
})
resultsController.tableView.reloadData()
searchController.automaticallyShowsCancelButton = true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedItems.contains(array[indexPath.row]) {
selectedItems.remove(at: selectedItems.firstIndex(of: array[indexPath.row])!)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
} else {
selectedItems.append(array[indexPath.row])
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
tableView.reloadData()
print(selectedItems)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == resultsController.tableView {
return filteredArray.count
} else {
return array.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = array[indexPath.row]
if selectedItems.contains(array[indexPath.row]) {
cell.accessoryType = .checkmark
}else{
cell.accessoryType = .none
}
return cell
}
any thoughts?
Use this function to filter stuff:
extension SearchVC: UISearchBarDelegate{
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
fetchedData = []
if searchText == ""{
fetchedData = items
} else {
for words in items{
if
words.item.lowercased().contains(searchText.lowercased()){
filteredData.append(words)
}
}
}
table.reloadData()
}
}
Where fetchedData is an empty string array and items is your array.
If the search bar is empty fetchedData will be filled with all of your items, else just with the matched ones.
Now, the most important thing to do is to use fetchedData instead of items to display the results and the count properly. So, for instance:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return filteredData.count
}
Furthermore, as other users pointed out in the comments, you should really check your cellForRowAt. Try this link: https://stackoverflow.com/a/34345245/10408872

Search bar filter isn't clearing the table when results don't match

I have a working search bar, and now I just need help clearing the table when the search text does not match any item in the array (not including an empty search bar).
I would also like for one of the cells to display a message when no match is found (like "no results available").
Here is my code:
#IBOutlet var searchForTool: UISearchBar!
#IBOutlet var toolTable: UITableView!
var searchActive : Bool = false
{
didSet {
if searchActive != oldValue {
toolTable?.reloadData()
}
}
}
typealias Item = (data: String, identity: String)
var filtered: [Item] = []
var items: [Item] = [
(data: " Data1", identity: "A"), (data: " Data2", identity: "B")
]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(true, animated: false)
AppState.shared.category = "Alphabetical"
}
#IBAction func backButton(_ sender: Any) {
if let navController = self.navigationController {
for controller in navController.viewControllers {
if controller is ToolsViewController {
navController.popToViewController(controller, animated: true)
}
}
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filtered = items.filter { item in
item.data.localizedCaseInsensitiveContains(searchText)
}
searchActive = !filtered.isEmpty
self.toolTable.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
}
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.alphabeticalLabel.layer.masksToBounds = true
if(searchActive) {
cell.alphabeticalLabel.text = filtered[indexPath.row].data
cell.alphabeticalLabel.layer.cornerRadius = 10
} else {
cell.alphabeticalLabel.text = items[indexPath.row].data
cell.alphabeticalLabel.layer.cornerRadius = 10
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vcName: String
if searchActive {
vcName = filtered[indexPath.row].identity
} else {
vcName = items[indexPath.row].identity
}
let viewController = storyboard?.instantiateViewController(withIdentifier: vcName)
self.navigationController?.pushViewController(viewController!, animated: true)
}
}
I know this isn't the best way of creating search bar functionality, but this is what I've been working with for a while. I'm sure the solution isn't very complicated, but I'm just not having any luck with it.
Any help would be greatly appreciated.
Based on your reply, add the check to see if the search text is empty or not:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
}
else if (searchForTool.text? == "") { // check if this is "" or nil
return items.count
}
else {
return 0 // Or 1 if you want to show a cell with "no found" text
}
}
You'll need to adjust the cellForRowAtIndexpath similarly. And check wether when the text property of the search bar is nil or the empty string when user has not typed anything

Using searchController to filter tableView, but the tableView isn't updating

I have a UITableViewController that is displaying the titles of Tags I created. When I first navigate to the UITableViewController, it displays the Array of Tags just fine, but when I use the UISearchController to filter through Tags, the Array I created to store the filtered results updates and holds the correct data, but the TableView doesn't change. here are the two functions that are most likely causing the problem, but just in case, I will have the entire class (not long) down below.
numberOfRowsInSection:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchController.searchBar.text != "") {
return filteredTags.count
}
return Tags.count
}
cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tagcell", for: indexPath) as! TagCell
var text = ""
if (searchController.searchBar.text != ""){
text = filteredTags[indexPath.row].title
} else {
text = Tags[indexPath.row].title
}
cell.cellLabel.text = text
return cell
}
Whole Class:
class TagCell: UITableViewCell{
#IBOutlet weak var cellLabel: UILabel!
}
class TagTableVC: UITableViewController{
//Table Content
var Tags: [Tag] = [globTS.animals, globTS.civilrights, globTS.guncontrol, globTS.gunrights, globTS.LGBTQ, globTS.prochoice, globTS.prolife]
var filteredTags = [Tag]()
//Searchbar Initialization
let searchController = UISearchController(searchResultsController: nil)
//Required Functions
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchController.searchBar.text != "") {
return filteredTags.count
}
return Tags.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tagcell", for: indexPath) as! TagCell
var text = ""
if (searchController.searchBar.text != ""){
text = filteredTags[indexPath.row].title
} else {
text = Tags[indexPath.row].title
}
cell.cellLabel.text = text
return cell
}
//Filters Tags array into Filtered array based on search query
func filterContentForSearchText(searchText: String, scope: String = "All"){
filteredTags = Tags.filter{ $0.title.lowercased().contains(searchText.lowercased())}
}
}
extension TagTableVC: UISearchResultsUpdating {
//calls the filter function everytime the searchbar is activated
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
After reevaluating the filteredTags, you should call reloadData on your tableview
func filterContentForSearchText(searchText: String, scope: String = "All"){
filteredTags = Tags.filter{ $0.title.lowercased().contains(searchText.lowercased())}
self.tableView.reloadData()
}

tableview filtered rows get reloaded after selecting row

I have implemented a search bar for my tableview by:
let searchController = UISearchController(searchResultsController: nil)
and in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
tblSearchTable.delegate = self
tblSearchTable.dataSource = self
loadListOfCountries()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tblSearchTable.tableHeaderView = searchController.searchBar
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
}
My problem is that, when I search for items, the table shows the filtered rows but when I tap/click a row, the tableview rows get reloaded (all items). This is my didselectrow function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("selected row")
self.dismiss(animated: false, completion: nil)
}
The text selected row gets printed but the the viewcontroller isnt getting dismissed but instead, just like I said, reloads all the original items in tableview.
Here are the extensions, might need them when helping me:
#available(iOS 10.0, *)
extension Search: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchText: searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
#available(iOS 10.0, *)
extension Search: UISearchResultsUpdating {
public func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
And here's the search function:
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredArray.removeAll()
var x = 0
for a in dataArray{
if( a.lowercased().contains(searchText.lowercased())){
filteredArray.append(a)
}
x = x + 1
}
self.tblSearchTable.reloadData()
}
Here's the complete code:
import UIKit
import CoreData
#available(iOS 10.0, *)
class Search : UIViewController, UITableViewDelegate, UITableViewDataSource{
var dataArray = [String]()
var dataLine = [String]()
var dataColor = [String]()
var filteredArray = [String]()
var filteredLine = [String]()
var filteredColor = [String]()
let searchController = UISearchController(searchResultsController: nil)
#IBOutlet var tblSearchTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tblSearchTable.delegate = self
tblSearchTable.dataSource = self
loadListOfCountries()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tblSearchTable.tableHeaderView = searchController.searchBar
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: UITableView Delegate and Datasource functions
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("selected row")
self.dismiss(animated: false, completion: nil)
//var ClassViewController = self.storyboard!.instantiateViewController(withIdentifier: "ViewController") as! ViewController
//ClassViewController.funcforsearch()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return filteredArray.count
}
return dataArray.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idCell", for: indexPath as IndexPath)
if searchController.isActive && searchController.searchBar.text != "" {
cell.textLabel?.text = filteredArray[indexPath.row]
} else {
cell.textLabel?.text = dataArray[indexPath.row]
}
//cell.textLabel?.text = candy.name
//cell.detailTextLabel?.text = candy.category
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60.0
}
// MARK: Custom functions
func loadListOfCountries() {
//get some array here
self.tblSearchTable.reloadData()
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredArray.removeAll()
var x = 0
for a in dataArray{
if( a.lowercased().contains(searchText.lowercased())){
filteredArray.append(a)
}
x = x + 1
}
self.tblSearchTable.reloadData()
}
}
#available(iOS 10.0, *)
extension Search: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchText: searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
#available(iOS 10.0, *)
extension Search: UISearchResultsUpdating {
public func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
Thanks!

How to return multiple values in swift

I'm starter in swift.
I create tableview and get data from jsonFile to show text and picture.
Then I want to add searchBar on tableview but have problem.
import UIKit
class EpisodesTableViewController: UITableViewController
{
var episodes = [Episode]()
var names = [Episode]()
let searchController = UISearchController(searchResultsController: nil)
var filteredNames = [Episode]()
func filterContentForSearchText(searchText: String) {
filteredNames = self.names.filter { name in
return name.title!.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
override func viewDidLoad()
{
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
tableView.setContentOffset(CGPoint(x: 0, y: searchController.searchBar.frame.size.height), animated: false)
tableView.estimatedRowHeight = tableView.rowHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.separatorStyle = .None
self.episodes = Episode.downloadAllEpisodes()
self.tableView.reloadData()
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searchController.active && searchController.searchBar.text != ""{
return filteredNames.count
}else{
return names.count
}
return episodes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("Episode Cell", forIndexPath: indexPath) as! EpisodeTableViewCell
let episode = self.episodes[indexPath.row]
let data: Episode
if searchController.active && searchController.searchBar.text != "" {
data = filteredNames[indexPath.row]
}
else {
data = names[indexPath.row]
}
let titleName = data.title!
cell.episode = episode
cell.textLabel?.text = titleName
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendData"{
if let detailPage = segue.destinationViewController as? detailEpisodeViewController {
if let indexpath = tableView.indexPathForSelectedRow {
let episode = episodes[indexpath.row]
detailPage.episode = episode
}
}
}
}
}
extension EpisodesTableViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
this my code.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searchController.active && searchController.searchBar.text != ""{
return filteredNames.count
}else{
return names.count
}
return episodes.count
}
When I return filteredNames and names interface just show seachbar. If I return filtered names and episodes show error index out of range.
I don't know How to fix that.
If you want to return two values just return a touple like so:
return (DataType, DataType)
so this could be
func returnTouple() -> (String, AnyObject) {
return ("Hello World", 1)
}
then you would access it like so:
let (myString, myObject) = returnTouple()
and myString == "Hello World"
You could also access both throught .0 and .1 like returnTouple().0 == "Hello World"
Next,
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active && searchController.searchBar.text != "" {
return filteredNames.count
} else {
return names.count
}
return episodes.count
}
This function shouldn't work. You have an if {} else {} with a return statement in both sections. Unless you said if {} else if {} this makes the thrid return statement impossible to hit so it shouldn't be there.

Resources