4
votes

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
}
5

5 Answers

4
votes

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
}
2
votes

In Swift 4 or Swift 5, you can use like bellow.. Your tableview like bellow enter image description here

  1. Create a project
  2. Create add textfield, tableView connect to viewcontroller
  3. 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

0
votes

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
}
0
votes

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

0
votes

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