want to create a search bar like google in swift - swift

I have an array like ["apple","appear","Azhar","code","BCom"] etc. This array contain more than half a million of records.
Now what I want to do, is to place a UISearchBar like in google and then whenever user types a text, then the dropdown list would appear with all the results containing this text and user could select one from the list.
For example - if the user types "a", then "apple","appear" and "Azhar" would appear in a drop-down list.
I don't want to use a UITableView or anything else to load the records. Whenever user types any word it should collect records from the array and make a drop down to display them.
How can I do this?
Suggestions required please.

Pretty simple code that would do the trick, the search bar filter is easy, as for the drop down menu i use a third party Pod named 'DropDown' that is very easy to use : https://github.com/AssistoLab/DropDown
import UIKit
import DropDown
class ViewController: UIViewController, UISearchBarDelegate {
var data: [String] = ["apple","appear","Azhar","code","BCom"]
var dataFiltered: [String] = []
var dropButton = DropDown()
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
dataFiltered = data
dropButton.anchorView = searchBar
dropButton.bottomOffset = CGPoint(x: 0, y:(dropButton.anchorView?.plainView.bounds.height)!)
dropButton.backgroundColor = .white
dropButton.direction = .bottom
dropButton.selectionAction = { [unowned self] (index: Int, item: String) in
print("Selected item: \(item) at index: \(index)") //Selected item: code at index: 0
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
dataFiltered = searchText.isEmpty ? data : data.filter({ (dat) -> Bool in
dat.range(of: searchText, options: .caseInsensitive) != nil
})
dropButton.dataSource = dataFiltered
dropButton.show()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(true, animated: true)
for ob: UIView in ((searchBar.subviews[0] )).subviews {
if let z = ob as? UIButton {
let btn: UIButton = z
btn.setTitleColor(UIColor.white, for: .normal)
}
}
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.text = ""
dataFiltered = data
dropButton.hide()
}
}

Related

how to add a searchbar in a tableview with a parsed JSON file

I have successfully parsed a JSON file with the following data model into my project and my tableview.
import Foundation
struct ActionResult: Codable {
let data: [Datum]
}
struct Datum: Codable {
let goalTitle, goalDescription, goalImage: String
let action: [Action]
}
struct Action: Codable {
let actionID: Int
let actionTit: String
}
Now I am trying to create a searchbar to search on the "actionTitle". My tableview has section headers and rows.
Relevant code:
var filteredData: [Action]?
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
filteredData = ????
navigationItem.searchController = searchController
parseJSON()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == ""{
filteredData = ????
}
else {
for actions in ???? {
if actions.lowercased().contains(searchText.lowercased()) {
filteredData.append(actions)
}
self.tableView.reloadData()
}
I do not know what code to use where I have ????.
Thanks.
You want to keep an instance of all of the available data (allActions), and always show your filter data (filteredData) in the tableView. So when there is nothing to filter, filteredData is equal to allActions (unless you intend to hide all data when the search is empty).
When searchBar(_:,textDidChange:) is called, you can use filter(_:) to evaluate if the item should be included. Apple's description on the filter closure:
A closure that takes an element of the sequence as its argument and
returns a Boolean value indicating whether the element should be
included in the returned array.
I don't know if there is a specific reason for declaring filteredData: [Action]?, is it because the data is not populated until parseJSON() is called? If so--I suggest initializing an empty arrays and populating them when the data is available.
Also, does parseData() produce an instance of Datum? I believe this piece of your code is not included, so I am adding datum: Datum?.
If I am wrong, please provide more info what parseJSON() populates and I will update my answer.
var result: ActionResult? {
didSet {
guard let result = result else { return }
allSectionDataActionMap = Dictionary(uniqueKeysWithValues: result.data.enumerated().map { ($0.0, ($0.1, $0.1.actions)) })
updateFilteredData()
}
}
var allSectionDataActionMap = [Int: (datum: Datum, actions: [Action])]()
// Maps the section index to the Datum & filtered [Action]
var filteredSectionDataActions = [Int: (datum: Datum, actions: [Action])]()
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
// ...
parseJSON()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredSectionDataActions.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSectionDataActions[section]?.actions.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
if let action = filteredSectionDataActions[indexPath.section]?.actions[indexPath.row] {
// setup cell for action
cell.textLabel?.text = action.actionTitle
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateFilteredData(for: searchText.lowercased())
tableView.reloadData()
}
func updateFilteredData(for searchText: String = String()) {
if searchText.isEmpty {
filteredSectionDataActions = allSectionDataActionMap
} else {
for (index, (datum, actions)) in allSectionDataActionMap {
let filteredActions = actions.filter { $0.actionTitle.lowercased().contains(searchText) }
if filteredActions.isEmpty {
filteredSectionDataActions[index] = (datum, actions)
} else {
filteredSectionDataActions[index] = (datum, filteredActions)
}
}
}
}

How to make sure the SearchBar Textfield is empty

I have a SearchBar when i press the cancel button i have it set so that the textfield should have no text and all of my collection view cell's show
When the cancel button is tapped while there is still text in the TextField my collection view shows no cells until i go back into the search bar and press the delete button "X"
Would appreciate some help Thanks
Here is the code
print(" search button pressed")
navigationItem.titleView = searchBar
searchBar.searchTextField.backgroundColor = .black
searchBar.searchTextField.textColor = .white
searchBar.placeholder = "search for a contact"
searchBar.showsCancelButton = true
searchBar.becomeFirstResponder()
navigationItem.leftBarButtonItem = nil
navigationItem.rightBarButtonItem?.tintColor = .white
} else {
print(" cancel button pressed")
searchBar.searchTextField.text? = ""
navigationItem.titleView = nil
searchBar.showsCancelButton = false
navigationItem.rightBarButtonItem?.isEnabled = true
navigationItem.rightBarButtonItem?.tintColor = .purple
collectionView.reloadData()
Did you try assigning nil to searchBar.searchTextField.text? Maybe that can make a difference.
here's how I would check if I've done my implementation properly. Make sure I'm calling necessary delegate methods for UISearchBarDelegate and handle filter there.
extension MyViewController: UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = true
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.view.endEditing(true)
if (searchBar.text?.count)! > 0 {
searchHandler(searchText: searchBar.text!)
}
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
searchBar.showsCancelButton = false
self.view.endEditing(true)
filteredValues?.removeAll() // this removes any previous array items
filteredValues = listValues // this assigns your listValues to filteredValues
collectionView?.reloadData() // this reloads your collectionView
}
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (searchBar.text?.count)! > 0 {
searchHandler(searchText: searchBar.text!)
}
return true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchHandler(searchText: searchText)
}
fileprivate func searchHandler(searchText: String) {
//implement your filtering here
}
}
Again, there are different ways you can implement your search filtering and assign your filtered values to the collectionView data source.
Hope this helps. Thanks

What is the method for the NSSearchField TextDidChange?

What is the analogous method for the function searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) in macOS?
Got the answer:
#IBOutlet weak var searchBar: NSSearchField!
func controlTextDidChange(_ obj: Notification){
let searchObject:NSSearchField? = obj.object as? NSSearchField
if searchObject==self.searchBar{
}
}
The equivalent is the delegate method controlTextDidChange:
The object of the notification is the NSSearchfield. You have to cast the type
#IBOutlet weak var searchBar: NSSearchField!
func controlTextDidChange(_ notification : Notification){
guard let field = notification.object as? NSSearchField, field == self.searchBar else { return }
}
You need to add the controlTextDidChange method as shown in the pic below(last method) then the searchFieldDidEndSearching
keeps track of when you finish searching or if searchbar is empty and the searchFieldDidStartSearching(_ sender: NSSearchField) gets called when you just start editing the textfield after being empty
DispatchQueue.main.async {
self.searchTable.reloadData()
}
}
func searchFieldDidStartSearching(_ sender: NSSearchField) {
}
func controlTextDidChange(_ obj: Notification) {
guard let field = obj.object as? NSSearchField, field == self.searchBar else { return }
filteredPokemonList = allPokemonList.filter({ (pokemon) -> Bool in
return pokemon.name.lowercased().contains(field.stringValue.lowercased())
})
DispatchQueue.main.async {
self.searchTable.reloadData()
}
}
below is a pic of my Attribute Inspector to have a function called based on each letter entered to filter and reload the NSTableView

Table is empty when search bar is empty or nil

When I search for a shop the table gets the right shop. but when I edit my search words ( delete one letter for example ) the table becomes empty. Even when I delete the search words the table becomes empty! I have to go to a different View Controller and come back to the search bar to see the full table.
This is my code
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
shops = shops.filter { $0.shopname.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil}
tableView.reloadData()
}
Where should I implement if searchBar.text == "" || searchBar.text ==nill return the original table cells. or when search again I want it to perform another search.
UPDATE
my shops array isn't a string type it's a class type that has strings within it. because it's a custom cell with JSON data from api
var shops: [Shops]!
var isSearch = false
var auxiliar : [String] = []
var searchActive: Bool = false
class Shops {
private var _familiy_id: String?
private var _logo : String?
private var _shopname : String?
var familiy_id : String{
return _familiy_id!
}
var shopname : String{
return _shopname!
}
var Logo : String{
return _logo!
}
init(shopname : String , Logo : String , family_id : String) {
self._shopname = shopname
self._logo = Logo
self._familiy_id = family_id
}
}
The problem is in this line:
shops = shops.filter { ... }
As you are only applying the filter and overlapping the original array then you will lose the elements. An auxiliary array is needed that helps keep the original.
A simple example: (code updated)
import UIKit
class Shops {
private var _familiy_id: String?
private var _logo : String?
private var _shopname : String?
var familiy_id : String{
return _familiy_id!
}
var shopname : String{
return _shopname!
}
var Logo : String{
return _logo!
}
init(shopname : String , Logo : String , family_id : String) {
self._shopname = shopname
self._logo = Logo
self._familiy_id = family_id
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var shops : [Shops]! = []
var auxiliar : [Shops]!
override func viewDidLoad() {
super.viewDidLoad()
// 1 - load data to shops array
shops.append(Shops(shopname: "Brasil", Logo: "BR", family_id: "1"))
shops.append(Shops(shopname: "Brasolia", Logo: "BA", family_id: "2"))
shops.append(Shops(shopname: "Colombia", Logo: "CO", family_id: "3"))
shops.append(Shops(shopname: "Argentina", Logo: "AR", family_id: "4"))
// 2 - auxiliar receive the complete original array
auxiliar = shops
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return auxiliar.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = auxiliar[indexPath.row].shopname
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
auxiliar = shops.filter { $0.shopname.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil }
if searchText == "" {
// 3 if there is nothing to search, auxiliar receive the complete orihinal array
auxiliar = shops
}
tableView.reloadData()
}
}
In Swift 3 you can use the code like this. You just try this code.
import UIKit
class SearchBarTableView: UIViewController, UITableViewDataSource,UITableViewDelegate, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var shops : [String] = ["Brasil", "Argentina", "Colombia", "Chile", "Equador", "Peru", "Uruguai", "Paraguai", "Venezuela"];
var auxiliar : [String] = []
var searchActive: Bool = false
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
self.searchBar.delegate = self
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar)
{
self.searchActive = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar)
{
self.searchActive = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar)
{
self.searchActive = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
self.searchActive = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
{
self.auxiliar = self.shops.filter({ (text) -> Bool in
let temp: NSString = text as NSString
let range = temp.range(of: searchText, options:.caseInsensitive)
return range.location != NSNotFound
})
if self.auxiliar.count == 0
{
searchActive = false
}
else
{
searchActive = true
}
self.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive
{
return auxiliar.count
}
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
if searchActive
{
cell.textLabel?.text = auxiliar[indexPath.row]
}
else
{
cell.textLabel?.text = shops[indexPath.row]
}
return cell
}

Swift how to update the Search result when touch return key

I Swift, i make a Search View using UISearcheController and UISearcheResultUpdating
and i update the result with func updateSearchResultsForSearchController
if self.searchController?.searchBar.text.lengthOfBytesUsingEncoding(NSUTF32StringEncoding) > 0 {
let searchBarText = self.searchController!.searchBar.text
var arrResult = DFManager.GetResult(searchBarText)
self.results?.addObjectsFromArray(arrResult)
// Reload a table with results.
self.searchResultsController?.tableView.reloadData()
}
But that is result always updating when i type char by char, it make this app slowly. I want this result only update when type return key?
Try this:
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
//do something
searchBar.resignFirstResponder() //hide keyboard
}
And dont forget to add the searchBar delegate:
class SomeViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
searchBar.delegate = self
}
}