Searchbar filtering issue - ios

Im new to the swift, I am trying to filter name from an array using the search bar in console am getting what I entered in the search bar but filtering with predicate im not getting filtered name...please can anyone help in this issue
var caseListOfBooker:[CaseDetails]=[]
var searchString:String=""
var filteredString = [String]()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print("searchText \(searchText)")
searchString = searchText
updateSearchResults()
tableview.reloadData()
}
func updateSearchResults(){
filteredString.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchString)
let array = self.caseListOfBooker.filter{$0.person_of_interest.contains(searchString)}
print(array)
if let list=array as? [String]{
filteredString=list
}
print(filteredString)
tableview.reloadData()
}
extension SearchPOIVC : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if filteredString != []{
return filteredString.count
}
else
{
if searchString != "[]" {
return caseListOfBooker.count
}else {
return 0
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.00
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:POIProfileDetailsCell = tableview.dequeueReusableCell(withIdentifier: "POIProfileDetailsCell", for: indexPath) as! POIProfileDetailsCell
if filteredString != []{
cell.poiName.text = filteredString[indexPath.row]
return cell
}else{
if searchString != "[]"{
cell.poiName.text = self.caseListOfBooker[indexPath.row].person_of_interest
}
return cell
}
}

The most efficient way to filter custom classes is to use the same type for the data source array and the filtered array
var caseListOfBooker = [CaseDetails]()
var filteredBooker = [CaseDetails]()
Add a property isFiltering which is set to true when the search text is not empty
var isFiltering = false
and delete searchString and filteredString
var searchString:String=""
var filteredString = [String]()
In updateSearchResults filter the data source array (with native Swift functions), set isFiltering accordingly and reload the table view
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print("searchText \(searchText)")
updateSearchResults(searchText: searchText)
}
func updateSearchResults(searchText: String) {
if searchText.isEmpty {
filteredBooker.removeAll()
isFiltering = false
} else {
filteredBooker = caseListOfBooker.filter{$0.person_of_interest.range(of: searchText, options: .caseInsensitive) != nil }
isFiltering = true
}
tableview.reloadData()
}
In the table view data source methods display the data depending on isFiltering
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isFiltering ? filteredBooker.count : caseListOfBooker.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "POIProfileDetailsCell", for: indexPath) as! POIProfileDetailsCell
let booker = isFiltering ? filteredBooker[indexPath.row] : caseListOfBooker[indexPath.row]
cell.poiName.text = booker.person_of_interest
}

let array = self.caseListOfBooker.filter{$0.person_of_interest.contains(searchString)}
You are getting array of CaseDetails objects and trying to cast to array of String
It fails. You need to get string values from the CaseDetails object and join them
Use
filteredString = array.map { $0.person_of_interest }
Or
for caseDetail in array {
filteredString.append(caseDetail.person_of_interest)
}
Instead of
if let list = array as? [String]{
filteredString=list
}

var searchPredicate = NSPredicate()
searchPredicate = NSPredicate(format: "self CONTAINS[C] %#", your_searching_text)
let Final_Search_array = (Your_Array).filtered(using: searchPredicate)

Related

Search bar returning only first element from data array

currently I am trying to implement my search bar, but something is wrong and I can't figure it out what it is. Here is the code, and explanation.
//global variable for empty array, its type of Any cause I am getting data from network call
var filteredData: [Any]!
//these are my models, which I am using to display them on screen after mapping in network function
var bookedTrips: [BookedTripsForView]?
func viewDidLoad() {
super.viewDidLoad()
filteredData = bookedTrips
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBar.becomeFirstResponder()
filteredData = []
if searchText == "" {
filteredData = bookedTrips
}else {
for trip in (bookedTrips)! {
if trip.tripName.lowercased().contains(searchText.lowercased()){
filteredData.append(trip)
//if I type, lets say Barcelona, in console its printed correct result,
//but its displaying only first trip in my array, which is Berlin
print("filteredDataArray after appending print: \(String(describing: filteredData))")
}
}
}
self.tableView.reloadData()
}
I hope that my explanation is ok, if something's not clear, I will refactor my question. Thanks in advance.
Here is picture of my screen and console
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let filter = filteredData {
return filter.count
} else if let data = bookedTrips {
return data.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.tripInfo) as! TripsListDetailCell
if let trips = bookedTrips?[indexPath.row] {
cell.configure(trips: trips)
}
return cell
}
Short and simple (One line filter)
var filteredData = [BookedTripsForView]()
var bookedTrips = [BookedTripsForView]()
override func viewDidLoad() {
super.viewDidLoad()
bookedTrips = fetchFromAPIorDB()
filteredData = bookedTrips
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.tripInfo) as! TripsListDetailCell
cell.configure(trips: filteredData[indexPath.row])
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
filteredData = bookedTrips
}
else {
filteredData = bookedTrips.filter({ $0.tripName.lowercased().contains(searchText.lowercased()) })
}
self.tableView.reloadData()
}
var isSearching: Bool = false // As global variable
var bookedTrips: [BookedTripsForView]? = []
var filteredData: [BookedTripsForView]? = []
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBar.becomeFirstResponder()
filteredData = []
if searchText == "" {
isSearching = false
}else {
isSearching = true
filteredData = bookedTrips.filter { (trip) -> Bool in
if trip.tripName.lowercased().contains(searchText.lowercased()){
return true
}
return false
}
}
self.tableView.reloadData()
}
//Tableview delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching {
return self.filteredData.count
}
return bookedTrips.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.tripInfo) as! TripsListDetailCell
if isSearching {
cell.configure(trips: filteredData[indexPath.row])
}
else {
cell.configure(trips: bookedTrips[indexPath.row])
}
return cell
}
create enum for page mood like this for readable code:
enum PageMood {
case normal
case search
}
and create variable
var pageMode: PageMood = .normal
set normal for first first if search and change pageMode to search like this:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBar.becomeFirstResponder()
if searchText == "" {
pageMode = .normal
}else {
pageMode = .search
filteredData = bookedTrips?.filter({ item -> Bool in
return (item.tripName?.lowercased().contains(searchText.lowercased()) ?? false)
})
}
self.tableView.reloadData()
}
change define datasource like this:
var bookedTrips: [BookedTripsForView]?
var filteredData: [BookedTripsForView]?
and inside set numberOfItem:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if pageMode == .search {
return filteredData.count ?? 0
} else {
return bookedTrips.count ?? 0
}
}
if only one item findŲŒ Maybe your data has only one item similar to the search text.
And now, since I changed my variables to this :
var filteredData = [BookedTripsForView]()
var bookedTrips = [BookedTripsForView]()
I have one more problem in sections, added comment inside
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: Cells.sectionTitle) as! TripsListHeaderCell
if isSearching {
cell.configure(trips: filteredData[section])
}
else {
cell.configure(trips: bookedTrips[section])
}
return cell
}
How should I implement function viewForHeaderInSection? In response inside every trip I get status of trip (current, upcoming, previous). I would like to sort them by status. If I put this inside viewForHeaderInSection :
if isSearching {
cell.configure(trips: filteredData[section])
} else {
cell.configure(trips:bookedTrips[section])
}
return cell
I get index out of range on bookedTrips[section] If i comment that line, it works until I make mistake in search bar, lets say instead of Barcelona I type Bars, it throws error on filteredData[section] index out of range
In my response, every trip have trip status property which has type string, can I even sort them by that property?

Search bar in Swift not searching with a PHP file

I have no errors in my code but there's definitely something missing. Also there when I press done in the keyboard nothing happens. It might have to do with the search bar function or the table view function. I also have a Search.swift file and I will add it. Any suggestions would be very helpful, I feel really stuck.
SearchBarViewController:
let url = URL(string: "http://127.0.0.1/musicfiles/search.php")
var filteredData = [String]()
var shouldShowSearchResults = false
var search: [Search] = []
var filePath = "http://127.0.0.1/musicfiles/search.php"
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
let task = URLSession.shared.dataTask(with: url!) { (data, snapshot, error) in
let retrievedList = String(data: data!, encoding: String.Encoding.utf8)
print(retrievedList!)
self.parseSongs(data: retrievedList!)
}
task.resume()
}
func parseSongs (data: String) {
if (data.contains("*")) {
let dataArray = (data as String).split(separator: "*").map(String.init)
for item in dataArray {
let itemData = item.split(separator: ",").map(String.init)
let searchSong = Search(songname: itemData[0])
search.append(searchSong!)
for s in search {
print(s.searchSongName())
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return search.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DataCell
let song = search[indexPath.row].searchSongName()
cell.congigureCell(text: song)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let searchSong = search[indexPath.row].searchSongName()
let fileURLString = "\(filePath)\(searchSong)"
print(fileURLString)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
shouldShowSearchResults = false
view.endEditing(true)
filteredData.removeAll()
} else {
shouldShowSearchResults = true
filteredData = search.filter({ (songName) -> Bool in
songName.searchSongName().range(of: searchText) != nil
})
.map { $0.songname }
}
tableView.reloadData()
}
You are loading your data into an array called search.
When you filter your data, you are placing the filtered data into an array called filteredData.
Your tableview always shows the contents of search, so you never see the results of your filtering.
You could check whether filteredData is empty and then return data from that array or search in numberOfRows and cellForRow. Personally I would always use filteredData and make sure that it holds the contents of search when the filter string is empty.
var filteredData = [Search]()
func parseSongs (data: String) {
if (data.contains("*")) {
let dataArray = (data as String).split(separator: "*").map(String.init)
for item in dataArray {
let itemData = item.split(separator: ",").map(String.init)
let searchSong = Search(songname: itemData[0])
search.append(searchSong!)
for s in search {
print(s.searchSongName())
}
self.filterData = self.search
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DataCell
let song = filteredData[indexPath.row].searchSongName()
cell.congigureCell(text: song)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let searchSong = filteredData[indexPath.row].searchSongName()
let fileURLString = "\(filePath)\(searchSong)"
print(fileURLString)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if (searchBar.text ?? "").isEmpty {
view.endEditing(true)
filteredData = search
} else {
filteredData = search.filter({ (songName) -> Bool in
songName.searchSongName().range(of: searchText) != nil
})
}
tableView.reloadData()
}

Certain Letters cause Index Out of Range CollectionView/TableView UISearchController

I have a collectionview inside a tableview and trying to search XML data. My searchcontroller is on my main tableview's view controller and the results are shown on a separate search results view controller. Some letters will search without any problems. I am getting the following index out of range error however, when I search certain letters:
I debugged and found out the filtered data is sometimes different from the returned/showing data:
I tried to add different conditions to handle the different counts, but I either get a crash for the index in cellForItemAt or a crash at didSelectItemAt.
My code:
Main View Controller:
var filteredData = [AgeRangeData]()
func updateSearchResults(for searchController: UISearchController) {
filteredData.removeAll()
for missingFilteredData in allAgesArray {
filteredData.append(missingFilteredData)
}
if let searchText = searchController.searchBar.text {
filteredData = allAgesArray.compactMap {
var filterObjects = $0
filterObjects.ageRangeData = $0.ageRangeData.filter {
$0.title.range(of: searchText, options: .caseInsensitive) != nil
}
return filterObjects.ageRangeData.isEmpty ? nil : filterObjects
}
let searchResultsVC = searchController.searchResultsController as! SearchResultsVC
searchResultsVC.missingFilteredData = filteredData
DispatchQueue.main.async {
searchResultsVC.resultsTableView.reloadData()
}
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
self.searchBarActive = false
} else {
self.searchBarActive = true
updateSearchResults(for: searchController)
}
DispatchQueue.main.async {
self.missingTableView?.reloadData()
}
}
Search Results View Controller:
var missingKidData: [AgeRangeData] = []
var missingFilteredData: [AgeRangeData]? = []
var missingKidSearchData: AgeRangeData!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return missingFilteredData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchResultsCell", for: indexPath) as! SearchResultsTableViewCell
if (missingFilteredData?.count)! > indexPath.row {
let missingSearchKidData = missingFilteredData![indexPath.row].ageRangeData
cell.missingKidData = missingSearchKidData[indexPath.row]
cell.missingResultsImage.layer.cornerRadius = 5.0
cell.missingResultsImage.clipsToBounds = true
return cell
}
return UITableViewCell()
}

Filter result(s) in array of class (Swift 3)

In Class
class Objects {
var number: Int!
var name: String!
init(number: Int, name: String) {
self.number = number
self.name = name
}
}
In viewController
var allObjects = [Objects]()
var inSearchMode = false
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
if inSearchMode {
let fill: Objects!
fill = filteredObject[indexPath.row]
cell.configureCell(fill)
} else {
let fill: Objects!
fill = allObjects[indexPath.row]
cell.configureCell(fill)
return cell
}
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return filteredObject.count
}
return allObjects.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
tableView.reloadData()
view.endEditing(true)
} else {
inSearchMode = true
var lowerCase = Int(searchBar.text!)
filteredObject = allObjects.filter({$0.number == lowerCase})
tableView.reloadData()
print(filteredObject)
}
}
I would like to have a search bar that filters and shows only one result that contains the number we are looking for. I thought about using contains and put the number we input from the search bar.
I manage to get one object into the filteredObject, but it won't show up in the tableView
Look carefully at this part of your code:
if inSearchMode {
let fill: Objects!
fill = filteredObject[indexPath.row]
cell.configureCell(fill)
} else {
let fill: Objects!
fill = allObjects[indexPath.row]
cell.configureCell(fill)
return cell
}
You did not return a cell when you are in search mode, that is why you do not see anything when searching.
I would use a computed property to drive the tableview; This property is either all the objects or the filtered object(s):
var allObjects = [Objects]()
var filteredObjects: [Objects]?
var objects: [Objects] = {
return filteredObjects ?? allObjects
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
let fill = self.objects[indexPath.row]
cell.configureCell(fill)
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
self.filteredObjects = nil
} else {
var lowerCase = Int(searchBar.text!)
self.filteredObjects = allObjects.filter({$0.number == lowerCase})
}
tableView.reloadData()
}

UITableView filtering

I have to perform a search operation in tableview with searchbar.
Which have a label of a person's name and an image for these persons in its cell.
My code is
override func viewDidLoad() {
super.viewDidLoad()
ArrPersons = ["Mahatma Gandhi","Pramukh Swami","Akshay Kumar","Sachin Tendulkar","Chetan Bhagat","Sardar Vallabhai Patel","Amitabh Bachchan"]
arrPersonImages = ["1.png","2.png","3.png","4.png","5.png","6.png","7.png"]
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if (searchText.characters.count>0) {
let predicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchText)
ArrPersons = arrTemp
let array = (self.ArrPersons as NSArray).filteredArrayUsingPredicate(predicate)
print(array)
ArrPersons = array as! [String]
}
else
{
ArrPersons = arrTemp
}
self.tableviewww.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.ArrPersons.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableviewww.dequeueReusableCellWithIdentifier("mycell")as! buildVcCell
cell.personsImages.image = UIImage (named:arrPersonImages[indexPath.row] )
cell.labelPersonNamess?.text = self.ArrPersons[indexPath.row]
cell.addBtn.addTarget(self, action: #selector(BuildVc.AddbuttonClicked(_:)), forControlEvents: .TouchUpInside)
return cell
}
The problem is this code only perform a search on the array of label persons. arrPersonImages is not filtering according to the name of the person entered it the searchbar.
You should create a "Model" for the Person (using MVC pattern):
First, create "Person" Model:
struct Person {
var name: String?
var imageName: String?
}
instead of using two separated arrays for storing the persons's data, you can create an array of Person Model:
// add those vars to your ViewController:
var persons = [Person]()
var filteredPersons = [Person]()
var isFiltering = false
override func viewDidLoad() {
super.viewDidLoad()
persons = [Person(name: "Ahmad", imageName: "img.png"), Person(name: "Harry", imageName: "img.png")]
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if (searchText.characters.count>0) {
isFiltering = true
filteredPersons = persons.filter {
$0.name?.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
print(filteredPersons)
}
else
{
isFiltering = false
filteredPersons = persons
}
self.tableviewww.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isFiltering == true ? filteredPersons.count : persons.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
// getting the current person
let currentPerson = isFiltering == true ? filteredPersons[indexPath.row] : persons[indexPath.row]
// do the rest of the implementation...
//...
}
Note that this is Swift 3 Code.
I think it would make a lot of sense to combine the name and image for each character into a model-struct, and rather use this for the base of the cells. That being said, the following should help you on the way without altering your existing code too much. It will also shy away from actually changing your arrays...
A nice dynamic variable to make things a bit more automatic elsewhere:
var filteredPersons: [String] {
return arrPersons.filter{ $0.contains(searchString) }
}
Which will give you
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredPersons.count
}
Then in you cellForRowAtIndexPath function you can have the name and image created like this:
let name = filteredPersons[indexPath.row]
let imageIndex = arrPersons.index(of: name)
let image = UIImage(named: arrPersonImages[imageIndex])
The best way to do searching is using an array with dictionary properties to keep the search result. Just for your reference:
class PersonController: UITableViewController {
let ArrPersons = [["name": "Mahatma Gandhi", "image": "1.png"], ["name": "Akshay Kumar", "image": "2.png"]]
var searchResult: [[String: String]]!
override func viewDidLoad() {
super.viewDidLoad()
searchResult = ArrPersons
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if (searchText.characters.count>0) {
searchResult = arr.filter { (item) -> Bool in
let name = item["name"]
return name!.containsString(searchText)
}
}
else
{
searchResult = ArrPersons
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.searchResult.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("mycell")as! buildVcCell
let person = searchResult[indexPath.row]
cell.personsImages.image = UIImage(named: person["image"]) // Person image
cell.labelPersonNamess?.text = person["name"] // Person name
cell.addBtn.addTarget(self, action: #selector(BuildVc.AddbuttonClicked(_:)), forControlEvents: .TouchUpInside)
return cell
}
}

Resources