UISearchbar using UItextfield in swift 4 - ios

I am working on JSON. My Json data print into the tableview. I want to filtered that data with searchbar. So I put Textfield for using the Searchbar. I use reference from this website
http://findnerd.com/list/view/How-to-create-your-own-search-bar-in-Swift-Not-using-UISearchBar/20577/
My Search bar is working but not properly. I want to filter data after I write 3 Words in searchbar.If I write "Ku" then my tableview remain hide. If I write "kus" in searchbar then searchbar started searching and show me filtered data in tableview started from "kus". my searchbar related code are these
struct PatientData:Decodable {
var ID : String
var dt_bod : String
var e_gender : String
var int_glcode : String
var var_email : String
var var_fname : String
var var_phoneno : String
var var_uname : String
init(userdata : [String:Any]) {
self.ID = userdata["ID"] as! String
self.dt_bod = userdata["dt_bod"] as! String
self.e_gender = userdata["e_gender"] as! String
self.int_glcode = userdata["int_glcode"] as! String
self.var_email = userdata["var_email"] as! String
self.var_fname = userdata["var_fname"] as! String
self.var_phoneno = userdata["var_phoneno"] as! String
self.var_uname = userdata["var_uname"] as! String
}
var tabledata = [String]()
var tableFilterData = [String]()
var patientDetails = [PatientData]()
#IBAction func textfieldchanged(_ sender: Any) {
tableview.isHidden = true
}
my textfield change character function
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
let searchText = textField.text! + string
if searchText.count >= 3 {
tableview.isHidden = false
tableFilterData = tabledata.filter({ (result) -> Bool in
return result.range(of: searchText, options: .caseInsensitive) != nil
})
print(tableFilterData) // I got filtered data here but how to show this data into the tableview
tableview.reloadData()
}
else{
tableFilterData = []
}
return true
}
tableview part is
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return patientDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as UITableViewCell!
let aa = patientDetails[indexPath.row].var_fname + " , " + patientDetails[indexPath.row].dt_bod + " , " + patientDetails[indexPath.row].var_phoneno
self.tabledata.append(aa)
cell.textLabel?.text = aa
cell.textLabel?.font = searchTextfield.font
return cell
}

Try this:
#objc func textFieldActive() {
tableView.isHidden = tableFilterData.count > 0 ? false : true
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
let searchText = textField.text! + string
if searchText.count >= 3 {
tableView.isHidden = false
tableFilterData = tabledata.filter({ (result) -> Bool in
return result.range(of: searchText, options: .caseInsensitive) != nil
})
tableView.reloadData()
}
else{
tableFilterData = []
}
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return tableFilterData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = tableFilterData[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = data
return cell
}

In Swift 4 or Swift 5, you can use like bellow..
Your tableview like bellow
Create a project
Create add textfield, tableView connect to viewcontroller
add bellow code..
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var txtName: UITextField!
var originalArr = [[String:Any]]();
var searchArrRes = [[String:Any]]()
var searching:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
//Assign delegate don't forget
txtName.delegate = self
tableView.delegate = self
tableView.dataSource = self
//my array
originalArr = [
["name": "abdul", "number": "+8800000001"],
["name": "abdin", "number": "+8800000002"],
["name": "Enamul", "number": "+8800000003"],
["name": "enam", "number": "+8800000004"],
["name": "Rafi", "number": "+8800000005"],
["name": "Ehaque", "number": "+8800000006"],
["name": "ab", "number": "+8800000007"],
["name": "Emon", "number": "+8800000008"],
["name": "enamu1", "number": "+8800000009"],
["name": "rafi", "number": "+88000000010"],
["name": "karim", "number": "+88000000011"],
["name": "radev", "number": "+88000000012"],
["name": "da", "number": "+88000000013"],
["name": "aa", "number": "+88000000014"],
["name": "rafi", "number": "+88000000010"],
["name": "karim", "number": "+88000000011"],
["name": "radev", "number": "+88000000012"],
["name": "da", "number": "+88000000013"],
["name": "aa", "number": "+88000000014"]
]
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
//input text
let searchText = textField.text! + string
//add matching text to arrya
searchArrRes = self.originalArr.filter({(($0["name"] as! String).localizedCaseInsensitiveContains(searchText))})
if(searchArrRes.count == 0){
searching = false
}else{
searching = true
}
self.tableView.reloadData();
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//check search text & original text
if( searching == true){
return searchArrRes.count
}else{
return originalArr.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//custom cell Custom_cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Custom_cell
//check search text & original text
if( searching == true){
var dict = searchArrRes[indexPath.row]
cell.label_name.text = dict["name"] as? String
cell.label_number.text = dict["number"] as? String
}else{
var dict = originalArr[indexPath.row]
cell.label_name.text = dict["name"] as? String
cell.label_number.text = dict["number"] as? String
}
return cell
}
}
You can download full source from GitHub Link: https://github.com/enamul95/TableView_Search

You can check size of text before filter:
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
var searchText = textField.text! + string
if string == "" {
searchText = (searchText as String).substring(to: searchText.index(before: searchText.endIndex))
}
if searchText == "" {
isSearch = false
tableview.reloadData()
}
else{
if searchText.count > 2 {
getSearchArrayContains(searchText)
}
}
return true
}

Please use this code:-
func getSearchArrayContains(_ text : String) {
tableFilterData = tableData.filter({$0.lowercased().contains(text)})
isSearch = true
tableview.reloadData()
}
For should three character use this linkenter link description here:-
Thanks

All of the above answers try to reverse engineer the UITextField string by concatenating the change to the previous string. This needed to be done because the delegate method shouldChangeCharactersIn is called before the change is made on the UITextField string.
These implementations are wrong and do not work when the user scrolls the cursor to the left and continue typing or selects and replaces text (as the answers ignore the NSRange delegate variable).
A better implementation is to not use the delegate method at all and instead add a target to the UITextField. This works because UITextField inherits from UIControl.
override func viewDidLoad() {
super.viewDidLoad()
searchTextField.addTarget(self, action: #selector(searchTextChanged(_:)), for: .editingChanged)
}
#objc func searchTextChanged(_ sender: UITextField) {
let search = sender.text ?? ""
filterContentForSearchText(search)
}
func filterContentForSearchText(_ searchText: String) {
print("Filterin with:", searchText)
filtered.removeAll()
filtered = original.filter { thing in
return "\(thing.value.lowercased())".contains(searchText.lowercased())
}
tableView.reloadData()
}
Note: This action can also be created in the storyboard by creating an #IBAction and dragging the Editing Changed connection from the UITextField to the #IBAction

Related

Filter multiple arrays that are populated using Firebase

I have a TextField which works as a SearchView and then my TableView shows my results. The prototype cell of the TableView consists of two Labels. The first label is populated by schoolNameArray. The second label is populated by schoolTownArray + ", " + schoolCountryArray. All 3 arrays are populated via Firebase. Image of the search functionality shown below.
Without the second label, I can filter one array just fine. My issue is I can only make my SearchView filter on one array, not three. E.g. I type 'Academy' and 'Azhar Academy' shows, but if I type 'Bolton' I get "Fatal error: Index out of range" because I haven't been able to populate the filteredSchoolLocationArray correctly.
My Android version of the project retrieves the text from the search box and then handles the filtering within the for loop of the firebase query. How can I do this (or achieve the same result) here using the following code:
class SearchViewController: UIViewController {
#IBOutlet weak var editTextSearch: UITextField!
#IBOutlet weak var tableViewSearch: UITableView!
var schoolNameArray = [String]()
var schoolTownArray = [String]()
var schoolCountryArray = [String]()
var filteredSchoolNameArray = [String]()
var filteredSchoolLocationArray = [String]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
let schoolDatabase = Database.database().reference().child("Timetable")
schoolDatabase.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let schoolID = child as! DataSnapshot
let stringApproved = schoolID.childSnapshot(forPath: "Approved").value
if stringApproved as? String == "Yes" {
let stringSchoolName = schoolID.childSnapshot(forPath: "Name").value as! String
let stringSchoolTown = schoolID.childSnapshot(forPath: "Town or City").value as! String
let stringSchoolCountry = schoolID.childSnapshot(forPath: "Country").value as! String
self.schoolNameArray.append(stringSchoolName)
self.schoolTownArray.append(stringSchoolTown)
self.schoolCountryArray.append(stringSchoolCountry)
}
}
self.tableViewSearch.reloadData()
})
}
}
extension SearchViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredSchoolNameArray.count
} else {
return schoolNameArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let searchItem = tableView.dequeueReusableCell(withIdentifier: "SearchItem") as! SearchItemCell
searchItem.searchItemSchoolName.text = schoolNameArray[indexPath.row]
searchItem.searchItemSchoolLocation.text = schoolTownArray[indexPath.row] + ", " + schoolCountryArray[indexPath.row]
if searching {
searchItem.searchItemSchoolName.text = filteredSchoolNameArray[indexPath.row]
searchItem.searchItemSchoolLocation.text = filteredSchoolLocationArray[indexPath.row]
} else {
searchItem.searchItemSchoolName.text = schoolNameArray[indexPath.row]
searchItem.searchItemSchoolLocation.text = schoolTownArray[indexPath.row] + ", " + schoolCountryArray[indexPath.row]
}
return searchItem
}
}
extension SearchViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
filteredSchoolNameArray = schoolNameArray.filter({$0.lowercased().contains((textField.text?.lowercased())!)})
searching = true
tableViewSearch.reloadData()
return true
}
}
You should be using a struct to represent your data. You'll have a much easier time. You have a few more kinks to work out but this should get you going:
class SearchViewController: UIViewController {
struct School {
let name: String
let town: String
let country: String
}
#IBOutlet weak var editTextSearch: UITextField!
#IBOutlet weak var tableViewSearch: UITableView!
var schools: [School] = []
var filteredSchools: [School] = []
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
// ...
if stringApproved as? String == "Yes" {
let name = schoolID.childSnapshot(forPath: "Name").value as! String
let town = schoolID.childSnapshot(forPath: "Town or City").value as! String
let country = schoolID.childSnapshot(forPath: "Country").value as! String
self.schools.append(.init(name: name, town: town, country: country))
}
// ...
}
// ...
}
extension SearchViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
filteredSchools = schools.filter({ $0.name.lowercased().contains(text.lowercased()) })
searching = true
tableViewSearch.reloadData()
return true
}
}
Thanks to Rob for his help, below is the final code.
class SearchViewController: UIViewController {
#IBOutlet weak var editTextSearch: UITextField!
#IBOutlet weak var tableViewSearch: UITableView!
struct School {
let schoolName: String
let schoolTown: String
let schoolCountry: String
}
var schoolArray: [School] = []
var filteredSchoolArray: [School] = []
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let schoolDatabase = Database.database().reference().child("Timetable")
schoolDatabase.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let schoolID = child as! DataSnapshot
let stringApproved = schoolID.childSnapshot(forPath: "Approved").value
if stringApproved as? String == "Yes" {
// stringSchoolID = schoolID.key
let stringSchoolName = schoolID.childSnapshot(forPath: "Name").value as! String
let stringSchoolTown = schoolID.childSnapshot(forPath: "Town or City").value as! String
let stringSchoolCountry = schoolID.childSnapshot(forPath: "Country").value as! String
self.schoolArray.append(.init(schoolName: stringSchoolName, schoolTown: stringSchoolTown, schoolCountry: stringSchoolCountry))
}
}
self.tableViewSearch.reloadData()
})
//filteredSchoolArray.removeAll()
}
}
extension SearchViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredSchoolArray.count
} else {
return schoolArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let searchItem = tableView.dequeueReusableCell(withIdentifier: "SearchItem") as! SearchItemCell
if searching {
let filteredSchoolItem = filteredSchoolArray[indexPath.row]
searchItem.searchItemSchoolName.text = filteredSchoolItem.schoolName
searchItem.searchItemSchoolLocation.text = filteredSchoolItem.schoolTown + ", " + filteredSchoolItem.schoolCountry
} else {
let schoolItem = schoolArray[indexPath.row]
searchItem.searchItemSchoolName.text = schoolItem.schoolName
searchItem.searchItemSchoolLocation.text = schoolItem.schoolTown + ", " + schoolItem.schoolCountry
}
return searchItem
}
}
extension SearchViewController: UITextFieldDelegate {
func textFieldShouldClear(_ textField: UITextField) -> Bool {
editTextSearch.resignFirstResponder()
editTextSearch.text = ""
filteredSchoolArray.removeAll()
tableViewSearch.reloadData()
return true
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
filteredSchoolArray = schoolArray.filter({$0.schoolName.lowercased().contains(text.lowercased()) ||
$0.schoolTown.lowercased().contains(text.lowercased()) ||
$0.schoolCountry.lowercased().contains(text.lowercased())})
searching = true
tableViewSearch.reloadData()
return true
}
}

Search name or number in `UISearchbar`

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.

Search JSON data in tableView swift4

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

How to Filter a search with an objects properties efficiently in swift?

Hey guys i've searched for hours and still cant find a proper way to search though my data base. I have an array of contact objects that have a username and name property and I have a "add user" view controller where the GOAL is to loop through all the users in my data base , and when searching , it widdles down the users in a UITABLEVIEW this is what I have so far.
Cliff notes of code below:
I get all my user objects from my database and store them in an array of type [contact] called "results" (custom object) then i attempt to filter the results and store those into a new array called "filteredData" Contact has type "userName" (String) which I would like to filter results by
import UIKit
import Firebase
class SearchForUsersViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var results = [Contact]()
var filteredData = [Contact]()
var isSearching = false;
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self;
searchBar.returnKeyType = UIReturnKeyType.done
getUserList()
}
#IBAction func dismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func getUserList(){
//populates results
staticValuesForData.instance.dataBaseUserref.observe( .value) { (snapshot) in
if let userList = snapshot.children.allObjects as? [DataSnapshot]{
for user in userList{
let name = (user.childSnapshot(forPath: staticValuesForData.instance.fName).value as! String) + " "
+ (user.childSnapshot(forPath: staticValuesForData.instance.lname).value as! String)
let contact = Contact(name: name , uid: user.key,
pic: user.childSnapshot(forPath: staticValuesForData.instance.profileUrl).value as! String,
userName: user.childSnapshot(forPath: staticValuesForData.instance.userName).value as! String )
print(contact.name)
print("user" , user)
self.results.append(contact)
}
}
}
}
}
table view extension :
extension SearchForUsersViewController : UITableViewDataSource ,
UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching{
return results.count
}
return 0;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell" , for: indexPath) as! AddedMeTableViewCell;
cell.profilePicture.loadImageUsingCacheWithUrlString(urlString: filteredData[indexPath.item].picUrl)
if isSearching{
cell.userName.text = filteredData[indexPath.item].userName!
}
else
{
cell.userName.text = results[indexPath.item].userName!
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80;
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
Search extension (where the issue is )
extension SearchForUsersViewController : UISearchBarDelegate{
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == "" || searchBar.text == nil{
view.endEditing(true)
isSearching = false;
tableView.reloadData()
}
else{
isSearching = true
ifSearchContains(word: searchBar.text!)
tableView.reloadData()
print(filteredData)
print(results)
print(searchBar.text)
}
}
func ifSearchContains(word : String)
{
for result in results{
if result.name.contains(word){
filteredData.append(result)
}else{
}
}
}
}
I have the search function above but it is not filtering , nor is the idea of it very efficient. this application is going to have thousands of users, can you please help me filter a search in an efficient way? Thank you so much
Here is the contact custom object just in case
import Foundation
class Contact : NSObject , Comparable{
let name : String!
let uid : String!
let picUrl : String!
let userName : String!
init(name : String , uid : String , pic : String , userName : String) {
self.name = name
self.uid = uid
self.picUrl = pic
self.userName = userName
}
static func ==(lhs: Contact, rhs: Contact) -> Bool {
return lhs.name == rhs.name
}
static func <(lhs: Contact, rhs: Contact) -> Bool {
return lhs.name < rhs.name
}
}

How to use search bar to fetch data from json result using swift?

Requirement : i need to filter the JSON data in UITableView with UISearchBar so i placed UISearchBar (not UISearchBarController) to top of my view controller and i placed UITableView below to the UISearchBarand I also have api key which contains data in json format .
code in my view controller:
class FourthViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate,UITabBarControllerDelegate,UISearchDisplayDelegate{
var arrDict = NSMutableArray()
var FilteredData = NSMutableArray()
var userid:String!
#IBOutlet var SearchButton: UISearchBar!
#IBOutlet var SlideShow: ImageSlideshow!
#IBOutlet var MyTableView: UITableView!
#IBOutlet var PostButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.hidden = true
SearchButton.delegate = self
jsonParsingFromURL()
SlideShow.backgroundColor = UIColor.whiteColor()
SlideShow.slideshowInterval = 5.0
SlideShow.pageControlPosition = PageControlPosition.UnderScrollView
SlideShow.pageControl.currentPageIndicatorTintColor = UIColor.lightGrayColor()
SlideShow.pageControl.pageIndicatorTintColor = UIColor.blackColor()
SlideShow.contentScaleMode = UIViewContentMode.ScaleAspectFill
SlideShow.setImageInputs(alamofireSource)
}
func jsonParsingFromURL () {
if Reachability.isConnectedToNetwork() == true
{
Alamofire.request(.GET, "http://something.com", parameters: nil, encoding: .URL, headers: nil).response { (req, res, data, error) -> Void in
let dataString = NSString(data: data!, encoding:NSUTF8StringEncoding)
print(dataString)
self.startParsing(data!)
}
}
else{
let alert = UIAlertController(title: "No Internet Connection", message: "make sure your device is connected to the internet", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
func startParsing(data :NSData)
{
let dict: NSDictionary!=(try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
for i in 0 ..< (dict.valueForKey("ads") as! NSArray).count
{
arrDict.addObject((dict.valueForKey("ads") as! NSArray) .objectAtIndex(i))
}
MyTableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrDict.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
let strTitle : NSString=arrDict[indexPath.row] .valueForKey("categoryname") as! NSString
let photoImage : NSString=arrDict[indexPath.row] .valueForKey("image1") as! NSString
let SecondImage : NSString=arrDict[indexPath.row] .valueForKey("image2") as! NSString
let ThirdImage : NSString=arrDict[indexPath.row] .valueForKey("image3") as! NSString
let FourthImage : NSString=arrDict[indexPath.row] .valueForKey("image4") as! NSString
let URL_API_HOST2:String = "https://www.imagestring.com/"
// let FourthData = NSData(contentsOfURL: NSURL(string: URL_API_HOST2 + (FourthImage as String))!)
cell.image1.sd_setImageWithURL(NSURL(string: URL_API_HOST2 + (photoImage as String)))
cell.image2.sd_setImageWithURL(NSURL(string: URL_API_HOST2 + (SecondImage as String)))
// cell.image2.image = UIImage(data: SecData!)
cell.image3.sd_setImageWithURL(NSURL(string: URL_API_HOST2 + (ThirdImage as String)))
cell.image4.sd_setImageWithURL(NSURL(string: URL_API_HOST2 + (FourthImage as String)))
cell.CategoryName.text = strTitle as String
return cell
}
Issue : I have already loaded one api key which is known as category..now i need fetch subcategory data using search bar..subcategory has another api....
Apple statement : UISearchController object manages the display of search results based on interactions with a search bar. description here
If you'r using UISearchBar
import UIKit
class TDSearchVC: UIViewController ,UITableViewDataSource,UITableViewDelegate , UISearchResultsUpdating , UISearchBarDelegate{
//MARK:- Outlets
//MARK:-
#IBOutlet var tblSearch: UITableView!
//MARK:- Properties
//MARK:-
var dataArray = [String]()
var filteredArray = [String]()
var shouldShowSearchResults = false
var searchController: UISearchController!
//MARK:- VDL
//MARK:-
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
loadListOfCountries() // get the data from file
configureSearchController() // Config Controller in VC
}
//MARK:- VC Methods
//MARK:-
func loadListOfCountries() {
// Specify the path to the countries list file.
let pathToFile = Bundle.main.path(forResource: "Country", ofType: "txt")
if let path = pathToFile {
// Load the file contents as a string.
do{
let countriesString = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
self.dataArray = countriesString.components(separatedBy: "\n")
}
catch{
print("try-catch error is catched!!")
}
tblSearch.reloadData()
}
}
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search here..."
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
self.tblSearch.tableHeaderView = searchController.searchBar
}
//MARK:- table datasource
//MARK:-
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if shouldShowSearchResults {
return filteredArray.count
}
else {
return dataArray.count
}
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
if shouldShowSearchResults {
cell.textLabel?.text = filteredArray[indexPath.row]
}
else {
cell.textLabel?.text = dataArray[indexPath.row]
}
return cell
}
//MARK:- search update delegate
//MARK:-
public func updateSearchResults(for searchController: UISearchController){
let searchString = searchController.searchBar.text
// Filter the data array and get only those countries that match the search text.
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country as NSString
return (countryText.range(of: searchString!, options: .caseInsensitive).location) != NSNotFound
})
tblSearch.reloadData()
}
//MARK:- search bar delegate
//MARK:-
public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
tblSearch.reloadData()
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = false
tblSearch.reloadData()
}
}
If you'r using UITextField
import UIKit
class TDSearchVC: UIViewController , UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate{
#IBOutlet var textSearch: UITextField!
#IBOutlet var tblSearchResult: UITableView!
var arrData : [String] = []
var arrFilterData : [String] = []
var isSearch : Bool!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
isSearch = false
/*
* If date Data is in Json then use JSON Serialization
*/
arrData = ["Apple", "Banana", "Chikoo", "Brew", "Cherry", "Mango", "Lotus", "Peacock", "Temple", "Pine Apple","Glass", "Rose", "Church", "Computer", "Carrot"]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK:- textfield
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
var searchText = textField.text! + string
if string == "" {
searchText = (searchText as String).substring(to: searchText.index(before: searchText.endIndex))
}
if searchText == "" {
isSearch = false
tblSearchResult.reloadData()
}
else{
getSearchArrayContains(searchText)
}
return true
}
// Predicate to filter data
func getSearchArrayContains(_ text : String) {
var predicate : NSPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", text)
arrFilterData = (arrData as NSArray).filtered(using: predicate) as! [String]
isSearch = true
tblSearchResult.reloadData()
}
// MARK:- TableView Delegates
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if isSearch! {
return arrFilterData.count
}
else{
return arrData.count
}
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
if isSearch! {
cell.textLabel?.text = arrFilterData[indexPath.row]
}
else{
cell.textLabel?.text = arrData[indexPath.row]
}
return cell
}
}

Resources