11

I am working on an new App using UITableView and a UISearchBar in Swift and it's already working fine. But since the final project must have a complete customized searchbar, I had to move to UITextField as the input Outlet because of the customization possibilities.

The problem is that I just can't figure out how to code so that the UITextField behaves like a UISearchBar. Have no idea of how I could filter the UITextField inputs and make the string usable with the UISearchBarDelegate methods.

So anyone could help me out with this?

EDIT: I followed Daniel's help and came up with this code, but it is returning "nil", not working.

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {

@IBOutlet weak var txtSearchBar: UITextField!
var searchTxt = ""
let prods = ["água", "terra", "ar", "fogo"]
var searchResults:[String] = []

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    txtSearchBar.delegate = self
}


func textFieldDidEndEditing(textField: UITextField) {
    searchTxt = textField.text
    println(searchTxt)
    searchResults = prods.filter({(produtos:String) -> Bool in
        
        let nameMatch = produtos.rangeOfString(self.searchTxt, options: NSStringCompareOptions.CaseInsensitiveSearch)
        
        println(nameMatch)
        return nameMatch != nil})
}

My input was the letters "ar" but it returned "nil", when it shouldn't since one of the array's object is "ar".

peterh
  • 11,875
  • 18
  • 85
  • 108
Marco Almeida
  • 1,285
  • 3
  • 17
  • 39
  • This is a pretty broad question. Are you asking how to detect when the user types in the text field, and then do something with that? Or how to make a scope bar? Or what? – Aaron Brager Oct 19 '14 at 01:38
  • @AaronBrager What I need is when the user hits the search button at the keyboard, the tableview will be reloaded with items matching the search string. I wanna a UITextField working as if it was a UISearchBar. – Marco Almeida Oct 19 '14 at 01:40

5 Answers5

13

Create uitextfield object:

@IBOutlet var SearchTxt: UITextField!
var search:String=""

@IBOutlet var ListTable: UITableView!
var AllData:Array<Dictionary<String,String>> = []
var SearchData:Array<Dictionary<String,String>> = []

Viewdidload:

override func viewDidLoad()
{
    super.viewDidLoad()

    AllData = [["pic":"list0.jpg", "name":"Angel Mark", "msg":"Hi there, I would like read your...", "time":"just now", "unread":"12"],
               ["pic":"list1.jpg", "name":"John Doe", "msg":"I would prefer reading on night...", "time":"56 second ago", "unread":"2"],
               ["pic":"list2.jpg", "name":"Krishta Hide", "msg":"Okey Great..!", "time":"2m ago", "unread":"0"],
               ["pic":"list3.jpg", "name":"Keithy Pamela", "msg":"I am waiting there", "time":"5h ago", "unread":"0"]
               ]

    SearchData=AllData
}

Search in textfield delegate method:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
    if string.isEmpty
    {
        search = String(search.characters.dropLast())
    }
    else
    {
        search=textField.text!+string
    }

    print(search)
    let predicate=NSPredicate(format: "SELF.name CONTAINS[cd] %@", search)
    let arr=(AllData as NSArray).filtered(using: predicate)

    if arr.count > 0
    {
        SearchData.removeAll(keepingCapacity: true)
        SearchData=arr as! Array<Dictionary<String,String>>
    }
    else
    {
        SearchData=AllData
    }
    ListTable.reloadData()
    return true
}

Search data display in tableview :

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return SearchData.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell

    var Data:Dictionary<String,String> = SearchData[indexPath.row]

    cell.Pic.image=UIImage(named: Data["pic"]!)
    cell.Name.text = Data["name"]
    cell.Msg.text = Data["msg"]
    cell.Time.text = Data["time"]

    cell.selectionStyle = .none
    return cell
}
Jayesh Miruliya
  • 3,279
  • 2
  • 25
  • 22
  • Hi Jayesh , Good answer really but there is a tiny issue if user presses backspace (X) it consider string as empty cause the new to be changed is nothing , so this part search = String(search.characters.dropLast()) is better to be search = String(textfield.text.dropLast()) – IsPha Nov 24 '17 at 08:15
  • Hi Jayesh, How to have multiple search options based on name - message - time? – kiran Sep 08 '21 at 20:03
3

Your primary question seems to be asking how to implement a class that works just like the UISearchBar. That would be a huge undertaking!

However, in your notes you just ask how to react to the search button getting tapped.

Add an IBAction and a second array to your view controller. Call the second array something like "foundItems". Connect the IBAction to the search button. When the action is called, read the text out of the text field and filter the items based on that text. Put the items that conform to the filter in the foundItems array, then call reloadData() on your table view.

In your table view data source methods, check to see if foundItems is not nil. If it isn't, then display them instead of your main items array.

You will also need some sort of cancel button. In that button's action, nil out the foundItems array and call reloadData() on your table view.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
3

Xcode 9.2 / Swift 4 -- Working Code

 @IBOutlet var tfSearchWorker: UITextField!
 @IBOutlet var tblView: UITableView!

 var AllData :Array<Dictionary<String,String>> = []
 var SearchedData:Array<Dictionary<String,String>> = []

@ViewDidLoad

 AllData = [["pic":"list0.jpg", "name":"Angel Mark", "msg":"Hi there, I would like read your...", "time":"just now", "unread":"12"],
           ["pic":"list1.jpg", "name":"John Doe", "msg":"I would prefer reading on night...", "time":"56 second ago", "unread":"2"],
           ["pic":"list2.jpg", "name":"Krishta Hide", "msg":"Okey Great..!", "time":"2m ago", "unread":"0"],
           ["pic":"list3.jpg", "name":"Keithy Pamela", "msg":"I am waiting there", "time":"5h ago", "unread":"0"]
           ]

self.SearchedData = self.AllData
self.tfSearchWorker.addTarget(self, action: #selector(searchWorkersAsPerText(_ :)), for: .editingChanged)

@Function

@objc func searchWorkersAsPerText(_ textfield:UITextField) {
    self.SearchedData.removeAll()
    if textfield.text?.count != 0 {
        for dicData in self.AllData {
            let isMachingWorker : NSString = (dicData.name!) as NSString
            let range = isMachingWorker.lowercased.range(of: textfield.text!, options: NSString.CompareOptions.caseInsensitive, range: nil,   locale: nil)
            if range != nil {
                SearchedData.append(dicData)
            }
        }
    } else {
        self.SearchedData = self.AllData
    }
    self.tblView.reloadData()
}

@UITableView

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
     return self.SearchedData.count
}

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
     let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell

     var Data:Dictionary<String,String> = SearchedData[indexPath.row]

     cell.Pic.image=UIImage(named: Data["pic"]!)
     cell.Name.text = Data["name"]
     cell.Msg.text = Data["msg"]
     cell.Time.text = Data["time"]

     cell.selectionStyle = .none
     return cell
}
Krunal Patel
  • 1,649
  • 1
  • 14
  • 22
1

With the help of @Daniel T. I managed to solve the problem with the following code:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    isFiltered = true
    searchResults = prods.filter({(coisas:String) -> Bool in
        let stringMatch = coisas.rangeOfString(textField.text)
        return stringMatch != nil

    })
    println(searchResults.description)
    textField.resignFirstResponder()
    table.reloadData()
    return true
}

Thanks @Daniel T.

Marco Almeida
  • 1,285
  • 3
  • 17
  • 39
0

This is how you can get actually an updated string. There are many scenarios that are not handled in the above answers. e.g.

  1. if you delete any character from the middle.
  2. if you delete multiple characters in the string.
  3. if you add something in the middle of the string.
  4. if you replace the substring.

In the following function, you will get the updated string in any case.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    var search = textField.text!
    if let r = Range(range, in: search){
        search.removeSubrange(r) // it will delete any deleted string.
    }
    search.insert(contentsOf: string, at: search.index(search.startIndex, offsetBy: range.location)) // it will insert any text.
    print(search)
    return true
}

Following function will be called if you tap on cross button inside of textfield

func textFieldShouldClear(_ textField: UITextField) -> Bool {
        print("search text field will be empty here")
        return true
    }
Aqeel Ahmad
  • 709
  • 7
  • 20