I have a search bar and a table view and I want to put names of songs , artists and albums in them. Whenever I type in the search bar it only filters threw album names and not songs, or artists. How would I get them all to show up because only getAlbumName() is showing up? I just added Search.swift.
var search = [Search]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if (searchActive) {
cell.textLabel?.text = search[indexPath.row].getCleanName()
cell.textLabel?.text = search[indexPath.row].getArtistId()
cell.textLabel?.text = search[indexPath.row].getAlbumName()
} else {
searchActive = false
}
return cell;
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
search = search.filter({ (songName) -> Bool in
return songName.songname.lowercased().range(of: searchText.lowercased()) != nil
})
if(search.count == 0) {
searchActive = false;
} else {
searchActive = true;
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Search.swift
class Search {
var search: Search!
var id = Int()
var name = String()
var cleanName = String()
var artist = String()
var album = String()
init?(id:String, name:String, artist:String, album:String) {
self.id = Int(id)!
self.name = name
self.cleanName = name.replacingOccurrences(of: "_", with: " ").replacingOccurrences(of: ".wav", with: "")
self.artist = artist
self.album = album
}
func getId() -> Int {
return id
}
func getName() -> String {
return name
}
func getCleanName() -> String {
return cleanName
}
func getArtistId() -> String {
return artist
}
func getAlbumName() -> String {
return album
}
}
If you want to search for multiple properties you have to add range(of expressions for each property
search = search.filter({ song -> Bool in
return song.cleanName.range(of: searchText, options: .caseInsensitive) != nil ||
song.artist.range(of: searchText, options: .caseInsensitive) != nil ||
song.album.range(of: searchText, options: .caseInsensitive) != nil
})
Consider that search = search.filter makes actually no sense because you overwrite the array with the filtered result and if you tap the backspace key you'll get unexpected behavior.
And most of your code in the class is redundant. The methods to access properties are pointless. It can be reduced to
class Search {
let id : Int
let name, cleanName, artist, album : String
init(id: String, name: String, artist: String, album: String) {
self.id = Int(id)!
self.name = name
self.cleanName = name.replacingOccurrences(of: "_", with: " ").replacingOccurrences(of: ".wav", with: "")
self.artist = artist
self.album = album
}
}
You can get the album easily with song.album
Related
I am trying to create a UITableView on my IOS app that displays a list of all the app's registered users from Firebase and includes a UISearchBar to search through them. I am very new to this and unfortunately this code is giving me the following error: "Value of type 'User' has no subscripts" on the line of code "let user = users[indexPath.row]"
There is an NSObject User class in another file that defines name and email as String?
These are the variables:
var searchActive : Bool!
var userList = [User]()
var filterUsers = [User]()
private var users = User()
private var hasFetched = false
Here is the search bar function I was using that is giving me the error:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterUsers = userList.filter({ (text) -> Bool in
let temp: NSString = text.name! as NSString
let range = temp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filterUsers.count == 0){
searchActive = false;
} else {
searchActive = true;
}
//refreshTable()
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filterUsers.count
}
return userList.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let user = userList[indexPath.row] as? [String : AnyObject] {
}
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell;
let user = users[indexPath.row]
cell.textLabel?.text = user.name
cell.textLabel?.text = user.name
return cell;
}
}
}
Modify this:
let user = users[indexPath.row]
To this:
let user = searchActive ? filterUsers[indexPath.row] : userList[indexPath.row]
In tableViewCell I have userNameLbl with name, userClgLbl with number. I want to search and show data in tableView either name search or number search.
If user search name - based on name I can show data in tableView.
If user search number - based on number I can show data in tableView.
But how to work with both name and number for single search bar. Actually here my data is dynamic from server and number is not phone number.
UISearchBarDelegate added to my class
let searchBar = UISearchBar()
var filteredData: [Any]!
#IBOutlet weak var listTblView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.userNameLbl.text = filteredData[indexPath.row] as? String
cell.userClgLbl.text = clg_uniq[indexPath.row] as? String
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let strArr:[String] = clg_uniq as! [String]
filteredData = searchText.isEmpty ? clg_uniq : strArr.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.range(of: searchText, options: .caseInsensitive) != nil
})
DispatchQueue.main.async {
self.listTblView.reloadData()
}
if searchText == "" {
DispatchQueue.main.async {
searchBar.resignFirstResponder()
}
}
}
//Added these lines after json parsing
self.filteredData = self.clg_uniq
self.listTblView.reloadData()
My example JSON data is
{"log" = (
{
Name = "Name1";
"clg_uniq" = 5c640e7b86e35;
},
{
Name = "Name2";
"clg_uniq" = <null>;
},
{
Name = <null>;
"clg_uniq" = 5c647af5d5c4d;
},
{
Name = "Name4";
"clg_uniq" = 5c647a0427253;
},
{
Name = <null>;
"clg_uniq" = <null>;
},
{
Name = "Name6";
"clg_uniq" = $cuniq";
},
)
}
Add following variables -
var logArray = [Dictionary<String, Any>]() // For all result
var searchedLogArray = [Dictionary<String, Any>]() // For filtered result
var searchActive = false // whenever user search anything
Replace UISearchBarDelegate -
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchActive = searchText.count > 0 ? true : false
let namePredicate = NSPredicate(format: "Name CONTAINS[c] %#", searchText)
let clgUniqPredicate = NSPredicate(format: "clg_uniq CONTAINS[c] %#", searchText)
let compoundPredicate = NSCompoundPredicate.init(orPredicateWithSubpredicates: [namePredicate, clgUniqPredicate])
searchedLogArray = logArray.filter({
return compoundPredicate.evaluate(with: $0)
})
listTblView.reloadData()
}
Replace UITableViewDataSource -
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchActive ? searchedLogArray.count : logArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
let logDict = searchActive ? searchedLogArray[indexPath.row] : logArray[indexPath.row]
// Name
if let name = log["Name"] as? String{
cell.userNameLbl.text = name
}else{
cell.userNameLbl.text = ""
}
// clg_uniq
if let clgUniq = log["clg_uniq"] as? String {
cell.userClgLbl.text = clgUniq
}else{
cell.userClgLbl.text = ""
}
return cell
}
I hope you are persing response as Dictionary<String, Any>
Let me know if you are still having any issue.
I am trying to show jsondata in to the tableView and search country from the searchBar but getting error in to the textDidChange function.
I want the user to enter three words into the searchBar then tableView will open and search data.
struct country : Decodable {
let name : String
let capital : String
let region : String
}
class ViewController: UIViewController,UISearchBarDelegate {
var isSearch : Bool = false
var countries = [country]()
var arrFilter:[String] = []
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var searchbar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
searchbar.delegate = self
let jsonurl = "https://restcountries.eu/rest/v2/all"
let url = URL(string: jsonurl)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do{
self.countries = try JSONDecoder().decode([country].self, from: data!)
}
catch{
print("Error")
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}.resume()
}
shows error into this part.
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
isSearch = false;
self.tableview.reloadData()
} else {
arrFilter = countries.filter({ (text) -> Bool in
let tmp: NSString = text
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(arrFilter.count == 0){
isSearch = false;
} else {
isSearch = true;
}
self.tableview.reloadData()
}
}
}
my table view part
extension ViewController : UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(isSearch){
return arrFilter.count
}
else{
return countries.coun
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(isSearch){
cell.textLabel?.text = arrFilter[indexPath.row]
}else{
cell.textLabel?.text = countries[indexPath.row].name.capitalized
}
return cell
}
}
First of all do not use NSString in Swift and the Foundation rangeOfString API, use native String and native range(of.
Second of all never check for an empty string and for an empty array with .count == 0. There is isEmpty.
Third of all please name structs and classes with a starting capital letter. struct Country ....
The error occurs because you are filtering Country instances and actually you are looking for its name or its region.
This is a pure Swift version of your code
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
isSearch = false
} else {
arrFilter = countries.filter( $0.name.range(of: searchText, options: .caseInsensitive) != nil }
isSearch = !arrFilter.isEmpty
}
self.tableview.reloadData()
}
If you want to filter for name and region write
arrFilter = countries.filter( $0.name.range(of: searchText, options: .caseInsensitive) != nil
|| $0.region.range(of: searchText, options: .caseInsensitive) != nil }
With this syntax declare arrFilter
var arrFilter = [Country]()
and in cellForRow write
let dataArray = isSearch ? arrFilter : countries
cell.textLabel?.text = dataArray[indexPath.row].name.capitalized
You are getting country object of your array as a string so such an error occured..
Please do as below
var arrFilter:[country] = [country]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(isSearch){
cell.textLabel?.text = arrFilter[indexPath.row].name.capitalized
}else{
cell.textLabel?.text = countries[indexPath.row].name.capitalized
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
isSearch = false;
self.tableview.reloadData()
} else {
arrFilter = countries.filter({ (country) -> Bool in
let tmp: NSString = NSString.init(string: country.name)
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(arrFilter.count == 0){
isSearch = false;
} else {
isSearch = true;
}
self.tableview.reloadData()
}
}
First you can not assign a value type [Country] to [String].For example when assign a arrFilter at that time country.filter always return country type value not a string type.
use below code to helping you,
var countries = [country]()
var arrFilter:[country] = [country]()
inside the viewdidLoad
override func viewDidLoad() {
self.countries.append(country(name: "India", capital: "New Delhi", region: "Asia"))
self.countries.append(country(name: "Indonesia", capital: "Jakarta", region: "region"))
self.countries.append(country(name: "Australia", capital: "Canberra", region: "Austrialia"))
// Do any additional setup after loading the view.
}
And
self.arrFilter = self.countries.filter({ (country) -> Bool in
let temp : NSString = country.name as NSString //or you can use country.capital or country.region
let range = temp.range(of: "ind", options: .caseInsensitive)
print(range.location)
print(range.length)
print(temp)
return range.location != NSNotFound
})
Thanks
Here I need to place a local search for my model class data that displayed in the table view and I tried to implement the search for that I placed successfully but the search was should be like when I enter the first letter it should filter depending on the first letter, not with th letter in all objects
for example in brand names I am having Fastrack, Microsoft, Dell, apple, fila
if I type f then it should display only fastrack and fila but it displays fastrack,fila and Microsoft which containing f
but I need like when the first letter should be equal to f then it should display fastback and file only not Microsoft and if the second letter equals with any another word then it should display filtered results
Here is my code
var bannerModel = [BrandBanners]()
var searchActive : Bool?
var filteredData = [BrandBanners]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive == true {
return filteredData.count
}
else {
return bannerModel.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! superBrandTableViewCell
if searchActive == true {
cell.brandImageView.kf.indicatorType = .activity
let item = filteredData[indexPath.row]
if URL(string: (item.brandImageforMobile)!) != nil {
let resource = ImageResource(downloadURL: URL(string: (item.brandImageforMobile)!)!, cacheKey: item.brandImageforMobile)
cell.brandImageView.kf.setImage(with: resource, placeholder: nil, options: nil, progressBlock: nil, completionHandler: nil)
}
else {
cell.brandImageView.image = #imageLiteral(resourceName: "placeholder")
}
cell.activityIndicator.stopAnimating()
self.activityIndicator.stopAnimating()
}
else {
cell.brandImageView.kf.indicatorType = .activity
let item = bannerModel[indexPath.row]
if URL(string: (item.brandImageforMobile)!) != nil {
let resource = ImageResource(downloadURL: URL(string: (item.brandImageforMobile)!)!, cacheKey: item.brandImageforMobile)
cell.brandImageView.kf.setImage(with: resource, placeholder: nil, options: nil, progressBlock: nil, completionHandler: nil)
}
else {
cell.brandImageView.image = #imageLiteral(resourceName: "placeholder")
}
cell.activityIndicator.stopAnimating()
self.activityIndicator.stopAnimating()
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
self.filteredData = self.bannerModel
} else {
var lowerCase = searchBar.text!
self.filteredData = bannerModel.filter({($0.brand?.lowercased().contains(lowerCase.lowercased()))!})
}
self.searchActive = true
brandTableView.reloadData()
}
You should use hasPrefix Method to achieve this
Example
struct User {
var userName : String = ""
var password : String = ""
}
var array = [User.init(userName:"Test",password:"123456"),User.init(userName:"TEMP",password:"123456"),User.init(userName:"Prashant",password:"123456"),User.init(userName:"Test",password:"123456")]
array = array.filter{$0.userName.hasPrefix("T")} // Observe hasPrefix instead of contains
print(array)
For your code
self.filteredData = bannerModel.filter({($0.brand?.lowercased().hasPrefix(lowerCase.lowercased()))!})
EDIT For Type Safety
self.filteredData = bannerModel.filter({($0.brand ?? "").lowercased().hasPrefix("H".lowercased())}
Hope it is helpful
You can do this:
self.filteredData = bannerModel.filter({($0.brand?.lowercased().hasPrefix(lowerCase.lowercased()))!})
I am using swift 2.0 . And i have added search bar to table view. i have run 2 times, its worked well. But now in my code its showing error :
Cannot invoke 'filter' with an argument list of type '(#noescape (Element) throws -> Bool)'
When i try to run also not able to search my table view data ,
Here is my full code:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate
{
var Table:NSArray = []
#IBOutlet weak var searchBar: UISearchBar!
var searchActive : Bool = false
var filtered:[String] = []
#IBOutlet weak var tableView: UITableView! // UITable view declaration
#IBOutlet weak var Resultcount: UILabel! // count label
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
var filteredTableData = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
CallWebService() // call the json method
// nib for custom cell (table view)
let nib = UINib(nibName:"customCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
searchBar.delegate = self
}
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filtered = Table.filter({ (text) -> Bool in
let tmp: NSString = text as! NSString
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
// every time app quit and run, switch will be in off state
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "PremiumUser")
}
func CallWebService()
{
let UrlApi = "url"
let Url = NSURL(string: UrlApi)
let Session = NSURLSession.sharedSession()
let Work = Session.dataTaskWithURL(Url!, completionHandler: { dataTask, response, error -> Void in
if (error != nil)
{
print(error)
}
var datos:NSData = NSData(data: dataTask!)
do {
let JsonWithDatos:AnyObject! = try NSJSONSerialization.JSONObjectWithData(datos, options: NSJSONReadingOptions.MutableContainers) as! NSArray
self.Table = JsonWithDatos as! NSArray
dispatch_async(dispatch_get_main_queue()) {
if (self.Table.count>0)
{
self.Resultcount.text = "\(self.Table.count) Results"
self.tableView.reloadData()
}
}
}catch{
print("Some error occured")
}
})
Work.resume()
}
// number of sections
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
}
return self.Table.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:customCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
if(searchActive){
cell.vendorName.text = filtered[indexPath.row]
} else {
cell.vendorName.text = Table[indexPath.row] as! String;
}
let item = self.Table[indexPath.row] as! [String : String]
cell.vendorName.text = item["name"]
cell.vendorAddress.text = item["address"]
return cell
}
}
i am getting error in this method func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { in this line filtered = Table.filter({ (text) -> Bool in
let tmp: NSString = text as! NSString
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
Why its work one time , that time also not able to do search in my table view. Now I am suddenly getting this error.
i have implemented search controller in table view
i give you my function that filter the searched data and make an array with matching strings
func filterDestinations(searchString: String){
self.filteredDestinations.removeAll()
if(self.defaultarray.count > 0){
for obj in self.sortedDest{
if(obj.name!.rangeOfString(searchString, options: .CaseInsensitiveSearch, range: nil, locale: nil) != nil){
self.filteredDestinations.append(obj)
}
}
}
}// ends filterDestinations
after that you just reload your table view and in function cellforrowatindex you check that if search controller is active than you give data from your filltered array otherwise use your default array.
also you have to set numberofrows by check search controller is active or not.
if its active then return the filltered array count otherwise return your default array count so your app wont crash .
Just remove the closure parameter and return value type. Also rangeOfString returns nil if the string was not found. And better to cast to Swift String not NSString. You are trying to assign NSArray (basically [AnyObject] to [String]. You have to do some mapping.
filtered = table.filter {
let tmp = $0 as! String
let range = tmp.rangeOfString(searchText, options: .CaseInsensitiveSearch)
return range != nil
}.map { $0 as! String }