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]
}
Related
I have search bar and its search order ids in my table view in that point I have another array which have some datas according to their order ids. My search bar working correctly, filtered array working correct but other array didn't work. (order ids and order contents have conflicts) The order of orders is shifting. How can I connect these two arrays?
Here my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: sipCellId, for: indexPath) as! sipCell
if isSearching {
searched = filteredArray[indexPath.row]
}else{
searched = self.orderId[indexPath.row]
}
cell.orderIdLabel.text = searched
cell.detailLabel.text = self.newDatas[indexPath.row]
return cell
}
Here my search bar functions:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if mainSearchBar.text == nil || mainSearchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
}else {
isSearching = true
filteredArray = orderId.filter({$0.range(of: mainSearchBar.text!, options: .caseInsensitive) != nil})
tableView.reloadData()
}
}
The context of your question is not clear enough you should clarify more, but generally speaking if you want to filter the original array according to another array's elements (per your headline question) you can simply filter the original array and ask if the other array contains the element.
originalArray = originalArray.filter{otherArray.contains($0)}
I know there's been questions like this before: here or here
But it hasn't gone into much detail, as much as I'd like anyway. I have this 'Explore' page screen sort of thing. It has a search bar at the top and I'd like to show content/posts in the middle section of it. But when the user clicks on the search bar, I want go to a tableview controller, replacing the content on the screen, to show the search results.
I had a tableviewcontroller embedded in the UIView container:
import UIKit
import Firebase
class SearchTableViewController: UITableViewController, UISearchResultsUpdating {
let searchController = UISearchController(searchResultsController: nil)
#IBOutlet var searchResultsTableView: UITableView!
var usersArray = [NSDictionary?]()
var filteredIsers = [NSDictionary?]()
var ref = Database.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
ref.child("users").child("public").queryOrdered(byChild: "username").observe(.childAdded, with: { (snapshot) in
if let user = snapshot.value as? [String:Any]{
let username = user["username"] as? String ?? ""
if(username == SpalshScreenViewController.UserData.username){
return
}else{
self.usersArray.append(snapshot.value as? NSDictionary)
}
}else{
return
}
print(self.usersArray)
// insert rows
self.searchResultsTableView.insertRows(at: [IndexPath(row: self.usersArray.count-1, section: 0)], with: UITableView.RowAnimation.automatic )
})
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if searchController.isActive && searchController.searchBar.text != "" {
return filteredIsers.count
}else{
return self.usersArray.count
}
}
func updateSearchResults(for searchController: UISearchController) {
filterContent(searchText: self.searchController.searchBar.text!)
}
func filterContent(searchText: String){
self.filteredIsers = self.usersArray.filter{ user in
let username = user!["username"] as? String
return(username?.lowercased().contains(searchText.lowercased()))!
}
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let user : NSDictionary?
if searchController.isActive && searchController.searchBar.text != "" {
user = filteredIsers[indexPath.row]
}else{
user = self.usersArray[indexPath.row]
}
cell.textLabel?.text = user?["firstName"] as? String
cell.detailTextLabel?.text = user?["username"] as? String
let profilePicUrl = user?["profilePicURL"] as? String
let encodedurl = URL(string: profilePicUrl!)
print(profilePicUrl)
cell.imageView!.kf.setImage(with: encodedurl, placeholder: UIImage(named: "profile_pic"))
return cell
}
}
Would I have that code in it's separate UITableViewController and then present that view when the search bar is clicked or?
I know you'd change the let searchController = UISearchController(searchResultsController: nil) to something like let searchController = UISearchController(searchResultsController: tableViewController)
but I don't know how to tie everything in. In my searchViewController I have reference to the UISearchBar, I was thinking I'd add a tap gesture onto it and present a the tablecontrollerview when it's clicked but that would mean there would be an inconsistency in animation and search bar style?
EDIT:
So basically, I have a view controller, in that view controller I have a search bar at the top (doing nothing at the moment) and underneath that search bar will be a collection view full of different posts. When the user clicks on the search bar I'd like to replace that collection view with a table view with the results of the search result. How would I achieve that?
You should set up searchResultsController and searchResultsUpdater properties of the UISearchController class
Concerning UISearchBar object, you have to use the one which comes with UISearchController and add it programmatically to a view.
view.addSubview(searchController.searchBar)
Hey #nathan if you can please explain in more details than it would be better. Other wise on base of my current understanding. If you have search bar added in top of view and you have the table view in it also then you can update your table view in search bar delegate methods. I am sharing the search bar delegate methods for your help.
extension SearchViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let str = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
viewModel?.search(text: str)
tableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let t = searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
if t.count > 0 {
viewModel?.addNewSearch(text: t)
tableView.reloadData()
}
}
searchBar.resignFirstResponder()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
viewModel?.cancel()
navigationController?.popViewController(animated: true)
} }
I have used MVVM in my code so ViewModel is basically used for data update and changes. Thanks you can ask if you have any confusion.
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.
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.