I have a tableView which is showing the list of employees. Now when I Scroll the tableView and if scroll not stopped yet and if I click the search bar, app crashes. Index out of range. I don't have any idea why this is happening. Please, if anyone can help. Thank you so much in advance.
Note: Tableview is loaded with listArray. When user search, I am searchArray to show search result.
Code:
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchedArray = []
searchEnable = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell:ListingCell = self.tableView.dequeueReusableCell(withIdentifier: "Cell") as! ListingCell
if searchEnable {
self.setEmployeeDetailsInTableView(cell: cell, index: indexPath.row, dataArray: searchedArray)
}
else{
self.setEmployeeDetailsInTableView(cell: cell, index: indexPath.row, dataArray: listArray)
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if (searchBar.text?.characters.count)! > 0 {
searchEnable = true
getListForSearchedText(searchText)
}
if searchBar.text?.characters.count == 0{
searchEnable = false
self.tableView.reloadData()
}
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchEnable = false
self.searchedArray = []
self.tableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
getListForSearchedText(searchBar.text!)
searchBar.resignFirstResponder()
}
Crash :
fatal error: Index out of range
You are facing this issue because you are setting searchEnable to true too early means in the searchBarTextDidBeginEditing(_:) so that as you said tableView is still scrolling, so it will call cellForRowAtIndexPath and because of searchEnable is true you are getting this crash. Now to solved your issue you need to set searchEnable to true where you are initializing the searchedArray array and reloading the tableView.
So you need to set searchEnable to true in the method searchBar(_:shouldChangeTextIn:replacementText:) or may be inside your method getListForSearchedText.
Related
I have the following code to search a database for recipes then return that data to my View Controller:
var discoveredRecipe: RecipeDiscovered? = nil
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
var searchText = searchBar.searchTextField.text
self.searchTask?.cancel()
if searchText != "" {
discoverModel.discoverRecipes(searchText: searchText ?? "") {
self.searchTextActive = true
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
searchBar.showsCancelButton = true
}
I can confirm that the data is returned to my View Controller and assigned to discoveredRecipe in the above
I then have the following to display that returned data in my Table View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("DiscoveredRecipe:\(discoveredRecipe)")
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
// If searchText entered
if searchTextActive == true {
print("DiscoveredRecipe:\(discoveredRecipe)")
cell.displayRecipeShort(recipeTitle: (discoveredRecipe?.hits![indexPath.row].recipe.label)!, recipeSourceUrl: (discoveredRecipe?.hits![indexPath.row].recipe.url)!, recipeImage: (discoveredRecipe?.hits![indexPath.row].recipe.image)!, indexPathRow: indexPath.row)
}
return cell
}
It doesn't matter where I put print("DiscoveredRecipe:\(discoveredRecipe)") within func tableView, it keeps printing as nil. What am I missing to get the newly assigned discoveredVariable within the func tableView scope?
Please check whether your delegate method which is updating the discoveredRecipe is called or not.
if it is not called that's mean delegation is not setup properly.
That might be the problem discoveredRecipe value is nil.
So I have a UITableView (not a tableView controller), and I have set its delegate, dataSource, and even registered its reusable cell, in my viewDidLoad:
searchTableView.delegate = self
searchTableView.dataSource = self
searchTableView.isHidden = true
searchTableView.isUserInteractionEnabled = false
searchTableView.register(UITableViewCell.self, forCellReuseIdentifier: "SearchResultCell")
Don't worry about the isUserInteractionEnabled, I change that to true when the user is supposed to be able to use it. Anyway, I am 100% sure that the prototype cell has the correct Reuse Identifier in the storyboard as well as my cellForRowAt function. Here are the table view dataSource functions:
//MARK: - UITableViewDelegate & UITableViewDataSource Methods
extension MapViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print("Yo")
return completionResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print(completionResults)
print("yo")
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath)
cell.textLabel?.text = completionResults[indexPath.row].subtitle
return cell
}
}
Yet nothing is populating the tableView when I am in the simulator. I think the tableView dataSource functions are just not being called, because I set print statements in them and they do not appear. I am also 100% sure that completionResults (an MKLocalSearchCompleter) does work, as I use it at the same time, in my searchBar textDidChange function and it prints results. I don't see why it would work in that function, and not my tableview ones, unless it does HAVE to be in a searchBar function. Anyway, just in case that matters, here's that function:
//MARK: - UISearchBarDelegate Methods
extension MapViewController: UISearchBarDelegate, MKLocalSearchCompleterDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.count > 0 {
searchTableView.isHidden = false
searchTableView.isUserInteractionEnabled = true
} else {
searchTableView.isHidden = true
searchTableView.isUserInteractionEnabled = false
}
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print(completionResults)
}
}
Does anyone have any idea why my searchTableView won't call the dataSource functions? Someone even suggested that it was constraints at one point, and I have made sure that those are correct, so that isn't a problem either. Thanks for any help!
You don’t appear to be calling searchTableView.reloadData(). It won’t reload the table unless you call that method.
Generally you’d set a delegate for the MKLocalSearchCompleter and call reloadData in completerDidUpdateResults(_:).
I have in my iOS app a mapView, and a UISearchBar to search locations and center the user on it. The results of my search are made by autocompletion and displayed in a tableView.
I had trouble with it, when i clicked on cancel, it displayed me all of my datas in the tableView.
So I implemented this function:
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchTableView.hidden = true
}
But the render of that function makes this:
I don't know (and don't know how to see) if my mapView is still under the black screen.
By the way, if I delete my cancel button function, and search something, if I click on Cancel it will display something like this:
To hide my tableView again, I have to make another search, than delete it with the little cross button, then cancel.
My code, of the autocompletion search, is like that:
func initTableView() {
searchTableView.delegate = self
searchTableView.dataSource = self
searchTableView.scrollEnabled = true
searchTableView.hidden = true
}
func searchBarSearchButtonClicked() {
searchTableView.hidden = false
searchBar.resignFirstResponder()
dismissViewControllerAnimated(true, completion: nil)
searchBar(searchBar, textDidChange: searchBar.text!)
}
func searchBar(_searchBar: UISearchBar, textDidChange searchText: String) {
searchTableView.reloadData()
searchAutocompleteEntriesWithSubstring(searchText)
}
func searchAutocompleteEntriesWithSubstring(substring: String) {
searchBar.filtredDatas.removeAll(keepCapacity: false)
for curString in searchBar.datas {
let myString:NSString! = curString.title! as NSString
let substringRange :NSRange! = myString.rangeOfString(substring)
if (substringRange.location == 0) {
searchBar.filtredDatas.append(curString)
}
}
}
This is from my ViewController.swift file.
My searchBar variable is a custom class that contains an NSArray of datas and another of fileterDatas.
#IBOutlet weak var mapView: UGOMapController!
#IBOutlet var searchTableView: UITableView!
#IBOutlet weak var searchBar: UGOSearchBar!
Thanks for your help.
If you need more of my code, I can edit this post.
Edit:
Here are my UITableView functions, used for searchTableView
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchBar.filtredDatas.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let autoCompleteRowIdentifier = "AutoComplete"
var cell = tableView.dequeueReusableCellWithIdentifier(autoCompleteRowIdentifier) as UITableViewCell!
if cell != nil {
let index = indexPath.row as Int
if (searchBar.filtredDatas.count > 0) {
cell!.textLabel!.text = searchBar.filtredDatas[index].title
}
}
else {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: autoCompleteRowIdentifier)
}
return cell!
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
searchBar.text = selectedCell.textLabel!.text
}
And my hierarchy:
And my viewController inspector:
You have 3 different outlets connected to the table view.
It means that when you hide the tableView, you also hide the view, because it's connected to the same Search Table View
You have to make sure that you connect your table view just to one outlet.
Edit
To be more precise:
You have to change your view controller from UITableViewController to UIViewController.
The view outlet will be connected to viewController's view property.
The tableView outlet will be removed, because in a normal UIViewController this property doesn't exist. (It's defined in UITableViewController)
SearchTableView should be connected to the searchTableView property that you define in your view controller.
I am trying to implement search in UITableViewController, but not based on filtering existing array, but rather calling my API to search values in remote database.
I thought I implemented everything correctly, because my textDidChange and cellForRowAtIndexPath methods are called in proper time and my data array has all the data I need. BUT - nothing shows up in my TableView! It always says "No Results". I can't find any existing solutions on SO since many classes were deprecated in iOS 8 and I'm struggling with implementing recent ones. Any help appreciated! My code:
class QuestionsController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
var searchController = UISearchController()
var searchText: String?
var areSearchResultsExhausted: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.searchController = UISearchController(searchResultsController: self)
self.searchBar.delegate = self
self.searchController.searchResultsUpdater = self
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = false
self.searchController.searchBar.sizeToFit()
self.getQuestionsData(0, searchText: nil)
}
var questionsDataObjects = [Question]()
// MARK: - Get data from API
func getQuestionsData(startFromQuestionId: Int, searchText: String?) {
// Getting data from API stuff like:
let task = session.dataTaskWithRequest(req) { ... }
questionsDataObjects.append(...)
self.tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionsDataObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("QuestionCell", forIndexPath: indexPath) as! BasicQuestionCell
// Populate the table view cells
let questionCell = questionsDataObjects[indexPath.row]
cell.titleLabel.text = questionCell.content
// Load more results when user scrolled to the end
if (indexPath.row == questionsDataObjects.count-1
&& questionsDataObjects.count > indexPath.row
&& !areSearchResultsExhausted) {
print("qDO.count: \(questionsDataObjects.count) ; indexPath.row: \(indexPath.row)")
self.getQuestionsData(indexPath.row + 1, searchText: self.searchText)
}
return cell
}
// Handle searching
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
self.searchText = searchText
self.areSearchResultsExhausted = false
questionsDataObjects.removeAll()
self.getQuestionsData(0, searchText: searchText)
tableView.reloadData()
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
self.searchText = nil
self.areSearchResultsExhausted = false
questionsDataObjects.removeAll()
self.getQuestionsData(0, searchText: searchText)
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
}
Two more important things:
1) updateSearchResultsForSearchController never gets called!
2) I have an error in console that says: Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController...>) but when I tried to solve this, my search bar stopped working COMPLETELY...
Got it. The problem was: when I dragged the search bar into Storyboard, I selected "Search Bar and Search Display Controller" instead of "Search Bar" itself. Search Display Controller, which is deprecated in iOS8 and later, made the code not work.
So, I had to delete the Search Display Controller from Document Outline, reconnect the outlets to new Search Bar and it worked. Hope it helps somebody.
Forgive me for not being able to figure this out on my own. I picked up iOS at Swift, and didn't learn much of objective-c.
I'm attempting to build in a uisearchbar that filters through a grouped table view composed of multiple arrays for it's sections. Essentially, I need to filter through an NSMutableArray built of other arrays in order to get my search results (normal array couldn't compile in 'reasonable time'). I took a look at this tutorial and it's getting at what I need, but unfortunately, I'm not the best when it comes to translating Obj-c over to swift, in this case. Any help (translation) or direction to other tutorials that will help me do this is greatly appreciated. I've been searching and searching, and tinkering all night -- but I've caved, I need some help.
Edit: still not answered
It is similar to objective-c code but lets start from here
After you create your view controller,table view and search bar components,
In ViewController class, you have to delegate items to ViewController like:
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
And then you have to implement(override) methods like
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filtered = data.filter({ (text) -> Bool in
let tmp: NSString = text
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
explained here Delegation
also here's a reference-video Youtube
hope it helps
Add these UISearchBarDelegate methods in your class and your class must confirm to this protocol using either code self.yourSearchBar.delegate = self or in storyboard.
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.text = ""
searchBar.resignFirstResponder()
self.searchedArrayToDisplay = self.listDataArray
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == "\n" {
searchBar.resignFirstResponder()
} else if searchText == "" {
self.searchedArrayToDisplay = self.listDataArray
} else {
self.filterContentForSearchText(searchText)
}
}
func filterContentForSearchText(searchText: String) {
self.searchedArrayToDisplay = self.listDataArray.filter({(eachObject: [String:AnyObject]) -> Bool in
let name = eachObject["name"] as! String
let stringMatch = name.lowercaseString.rangeOfString(searchText.lowercaseString)
let result = stringMatch != nil ? true : false
return result
})
}
Now in cell for at index path method
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell : UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Value1, reuseIdentifier:"cell")
var object = self.searchedArrayToDisplay[indexPath.row]
}