Create SearchController for reusable use - ios

Is it possible to create UISearchController for reusable use in many classes?
To use a minimum of code in new classes.
Now I have code in all classes, but I want to use minimum code in all classes to call searchController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
var photoBook: [PhotoBooking] = []
var filteredBook: [PhotoBooking] = []
private var searchController = UISearchController(searchResultsController: nil)
private var searchBarIsEmpty: Bool {
guard let text = searchController.searchBar.text else { return false }
return text.isEmpty
}
private var isFiltering: Bool {
return searchController.isActive && !searchBarIsEmpty
}
override func viewDidLoad() {
super.viewDidLoad()
configureSearchController()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredBook.count
}
return photoBook.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var pBook: PhotoBooking
if isFiltering {
pBook = filteredBook[indexPath.row]
} else {
pBook = photoBook[indexPath.row]
}
cell.textLabel?.text = pBook.invoiceID
cell.detailTextLabel?.text = pBook.contactInfo["surname"] as! String
return cell
}
private func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.searchBar.barTintColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
}
}
extension AllPhotoBookingsViewController: UISearchResultsUpdating {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
filterContentForSearchText(searchBar.text!)
tableView.reloadData()
}
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
tableView.reloadData()
}
private func filterContentForSearchText(_ searchText: String) {
filteredBook = photoBook.filter({ (book: PhotoBooking) -> Bool in
let name = book.contactInfo["name"] as! String
let surname = book.contactInfo["surname"] as! String
let invoice = book.invoiceID
return invoice.localizedCaseInsensitiveContains(searchText) ||
name.localizedCaseInsensitiveContains(searchText) ||
surname.localizedCaseInsensitiveContains(searchText)
})
tableView.reloadData()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
navigationController?.hidesBarsOnSwipe = false
}
}
Maybe need use protocol, but I don't understand how to use correctly protocol.

There are many ways of doing this, using protocols is a great approach, but you could also subclass UIViewController and make a BaseSearchableViewController where you would add all the functionality you need for your search, then, you just subclass from BaseSearchableViewController instead of UIViewController.

Related

UISearchBar to filter custom UITableViewCells

I am trying to filter custom UITableViewCells, based on one of the UILabels present in the cell.
All of the data is parsed from remote JSON, which has the model locally named Item and currently I am displaying the following in each cell successfully:
var titleLabel = UILabel()
var descriptionLabel = UILabel()
Also defined in my ItemTableViewCell class is:
func set(product: Item) {
DispatchQueue.main.async { [weak self] in
self?.titleLabel.text = item.title
self?.descriptionLabel.text = item.listPrice
}
}
These are called within my main View Controller, to which I have added a searchBar successfully and is visible within the app:
import Foundation
import UIKit
class ItemsListViewController: UIViewController {
var items = [Items]()
var itemsSearch: [Items] = []
var tableView = UITableView()
var searchController = UISearchController()
struct Cells {
static let itemCell = "ItemCell"
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
configureSearchController()
}
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.placeholder = "Search items"
definesPresentationContext = true
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
}
func configureTableView() {
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 100
tableView.pin(to: view)
tableView.register(itemTableViewCell.self, forCellReuseIdentifier: Cells.itemCell)
}
func setTableViewDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
}
extension itemsListViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.itemCell) as! itemTableViewCell
let item = items[indexPath.row]
cell.set(item: item)
return cell
}
}
extension ItemsListViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
}
}
How can I filter these cells so say for instance a user searches for "Food", the only items which return would be ones which have a cell titleLabel = "food"?
I've tried to implement a function similar to the below, however it fails to achieve what I am after:
func filterContentForSearchText(_ searchText: String, category: = nil) {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.productCell) as! ProductTableViewCell
productsSearch = products.filter { (cell: cell) -> Bool in
return products.name.lowercased().contains(searchText.lowercased())
}
tableView.reloadData()
}
Thanks in advance for any help here.
You can not filter TableViewCells. You have to filter your model data and instead of using UISearchResultsUpdating you should use UISearchBarDelegate
I have modified your code, check it.
class ItemsListViewController: UIViewController {
var items = [Items]()
var itemsSearch: [Items] = []
var filterActive = false
var tableView = UITableView()
var searchController = UISearchController()
struct Cells {
static let itemCell = "ItemCell"
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
configureSearchController()
}
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.placeholder = "Search items"
searchController.searchBar.delegate = self
definesPresentationContext = true
}
func configureTableView() {
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 100
tableView.pin(to: view)
tableView.register(itemTableViewCell.self, forCellReuseIdentifier: Cells.itemCell)
}
func setTableViewDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
}
extension ItemsListViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filterActive ? itemsSearch.count : items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.itemCell) as! itemTableViewCell
let item = filterActive ? itemsSearch[indexPath.row] : items[indexPath.row]
cell.set(item: item)
return cell
}
}
extension ItemsListViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterItems(text: searchController.searchBar.text)
}
func filterItems(text: String?) {
guard let text = text else {
filterActive = false
self.tableView.reloadData()
return
}
self.itemsSearch = self.items.filter({ (item) -> Bool in
return item.title.lowercased().contains(text.lowercased())
})
filterActive = true
self.tableView.reloadData()
}
// Edited Version
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
filterActive = false
self.tableView.reloadData()
}
}
You are doing it the wrong way.
You should not filter the cells, but instead filter the model (e.g. add and remove entries to the table view data source)
Then call reloadData - this will then access the filtered model (in cellForRowAt and create only the filtered amount of (visible) cells
To improve, use a diffable data source

Searching dynamic data issue from table view issue in swift

I have a search bar above my table view. The data in table view is coming from my service. I'm trying to apply search filter on the table view data. I have tried some code but it isn't working. My code for searchbar is this,
UIViewController,UISearchBarDelegate,UITextFieldDelegate,UITextViewDelegate,ShowsAlert
#IBOutlet weak var searchBar: UISearchBar!
var filteredData = [String]()
var isSearching = false
var dishNameArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
menuView.isHidden = true
reviewView.isHidden = true
infoView.isHidden = true
scrollView.isScrollEnabled = false
//TableView Delegates
menuTableView.delegate = self
menuTableView.dataSource = self
reviewTableView.delegate = self
reviewTableView.dataSource = self
reviewTableView.reloadData()
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
segmentControl.tintColor = #colorLiteral(red: 0.9529120326, green: 0.3879342079, blue: 0.09117665142, alpha: 1)
searchBar.delegate = self
dishNameLbl.text = name
dishDescripLbl.text = resDesc
minOrderLbl.text = minOrder
deliveryLbl.text = deliveryTime
}
private func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
isSearching = true
}
private func searchBarTextDidEndEditing(searchBar: UISearchBar) {
isSearching = false
}
private func searchBarCancelButtonClicked(searchBar: UISearchBar) {
isSearching = false
}
private func searchBarSearchButtonClicked(searchBar: UISearchBar) {
isSearching = false
}
private func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchBar.text else {
isSearching = false
return
}
filteredData = dishNameArray.filter({
return $0.lowercased().contains(searchText.lowercased())
})
isSearching = filteredData.count > 0
self.menuTableView.reloadData()
}
extension RestaurantMenuVC: UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == menuTableView{
if isSearching{
return filteredData.count
}
return ResMenuService.instance.categoryModelInstance.count
}
else{
return AllReviewsService.instance.allReviewsModel.count
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if tableView == menuTableView{
return 57
}
else{
return 137
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == menuTableView{
let cell = menuTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! RestaurantMenuTableViewCell
if isSearching{
cell.dishTitleLbl.text = filteredData[indexPath.row]
dishNameArray.append(cell.dishTitleLbl.text!)
}
// let cell = menuTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! RestaurantMenuTableViewCell
cell.dishTitleLbl.text = ResMenuService.instance.categoryModelInstance[indexPath.row].categoryName
cell.cardView.layer.cornerRadius = 5
cell.selectionStyle = .none
return cell
}
else
{
let cell = reviewTableView.dequeueReusableCell(withIdentifier: "reviewCell", for: indexPath) as! AllReviewsTableViewCell
cell.nameLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].name
cell.descriptionLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].description
cell.timeLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].time
cell.ratingView.rating = Double(AllReviewsService.instance.allReviewsModel[indexPath.row].rating)
cell.backgroundColor = UIColor.clear
cell.selectionStyle = .none
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == menuTableView{
let minimumSpending = String(ResMenuService.instance.restaurntDetailModelInstance[indexPath.row].minimumSpending)
UserDefaults.standard.set(minimumSpending, forKey: "minimumSpending")
UserDefaults.standard.synchronize()
let categoryModel = ResMenuService.instance.categoryModelInstance
let subCategoryModel = ResMenuService.instance.categoryModelInstance[indexPath.row].subCategories
let AddonCategoryModel = ResMenuService.instance.categoryModelInstance[indexPath.row].subCategories[0].items[0].addonCategory
// if categoryId == subCategoryId{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "RestaurantMenuDetailVC") as! RestaurantMenuDetailVC
vc.categoryModel = categoryModel
vc.subCategoryModel = subCategoryModel
vc.AddonCategoryModel = AddonCategoryModel
self.navigationController?.pushViewController(vc, animated: true)
//}
}
else{
print("Hello")
}
}
}
But when i type something it does not filter the data. here is my model class,
struct RestaurantDetailModel {
public private(set) var restaurantId:String!
public private(set) var shopLat:String!
public private(set) var shopLng:String!
public private(set) var street:String!
public private(set) var town:String!
public private(set) var zipCode:String!
public private(set) var cellNo:String!
public private(set) var landLine:Int!
public private(set) var shopName:String!
public private(set) var deliveryTime:Int!
public private(set) var collectionTime:Int!
public private(set) var facebookLink:String!
public private(set) var twitterLink:String!
public private(set) var googleLink:String!
public private(set) var instagramLink:String!
public private(set) var pinterestLink:String!
public private(set) var address:String!
public private(set) var preorderPref:String!
public private(set) var orderStatus:Bool!
public private(set) var minimumSpending:Int!
public private(set) var restaurantTimings:[RestaurantTimingsModel]!
}
You dont set isSearching to true anywhere. So it is always false.
So the table never uses filteredData until you set isSearching = true
func updateSearchResults(for searchController: UISearchController) is a UISearchControllerDelegate method. As you are not using a UISearchController this will not be called in your case. You need to be using the UISearchBarDelegate functions.
Try the below changes. Reference used
in viewDidLoad add the following line:
searchBar.delegate = self
Add the following functions:
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
isSearching = true
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
isSearching = false
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
isSearching = false
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
isSearching = false
}
Change your updateSearchResults function to this:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchBar.text else {
isSearching = false
return
}
filteredData = dishNameArray.filter({
return $0.lowercased().contains(searchText.lowercased())
})
isSearching = filteredData.count > 0
self.menuTableView.reloadData()
}
You also need to make your ViewController conform to UISearchBarDelegate so add it, something like this:
class ViewController: UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate
This is much easier when using a UISearchController. See the example below.
Example using UISearchController:
I have just put this example together in a playground which shows how to do this with a UISearchController.
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
var names = [
"John",
"Terry",
"Martin",
"Steven",
"Michael",
"Thomas",
"Jason",
"Matthew"
]
var filteredNames = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Search Example"
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
navigationItem.searchController = searchController
definesPresentationContext = true
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredNames.count
} else {
return names.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell() // don't do this, i am for example.
var name: String
if isFiltering() {
name = filteredNames[indexPath.row]
} else {
name = names[indexPath.row]
}
cell.textLabel?.text = name
return cell
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredNames = names.filter({( name : String) -> Bool in
return name.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
let vc = ViewController()
let nav = UINavigationController()
nav.viewControllers = [vc]
PlaygroundPage.current.liveView = nav
EDIT 2: Working SearchBar
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController, UISearchBarDelegate {
var searchBar: UISearchBar!
var isFiltering = false
var names = [
"John",
"Terry",
"Martin",
"Steven",
"Michael",
"Thomas",
"Jason",
"Matthew"
]
var filteredNames = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Search Example"
searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
searchBar.delegate = self
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
tableView.tableHeaderView?.addSubview(searchBar)
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
isFiltering = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isFiltering = false
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
isFiltering = true
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
isFiltering = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchBar.text else {
isFiltering = false
return
}
filteredNames = names.filter({
return $0.lowercased().contains(searchText.lowercased())
})
isFiltering = filteredNames.count > 0
self.tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredNames.count
} else {
return names.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell() // don't do this, i am for example.
var name: String
if isFiltering {
name = filteredNames[indexPath.row]
} else {
name = names[indexPath.row]
}
cell.textLabel?.text = name
return cell
}
}
let vc = ViewController()
PlaygroundPage.current.liveView = vc
EDIT 3: New information provided
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchBar.text else {
isFiltering = false
return
}
filteredData = ResMenuService.instance.categoryModelInstance.filter({
return $0.categoryName.lowercased().contains(searchText.lowercased())
})
isFiltering = filteredData.count > 0
self.tableView.reloadData()
}
Try this
func updateSearchResults(for searchController: UISearchController) {
guard let searchtext = searchController.searchBar.text else {
return
}
filteredData.removeAll(keepingCapacity: false)
let filterPredicate = NSPredicate(format: "self contains[c] %#", argumentArray: [searchtext])
filteredData = dishNameArray.filter { filterPredicate.evaluate(with: $0) }
print(filteredData)
DispatchQueue.main.async {
self.menuTableView.reloadData()
}
}
You need to add function to identify searching state as below:
func isSearching() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}

Swift - Having problems with SearchBar in a custom TableViewController

I am new to code and I want to make an Search function in my Custom TableViewController. I get an error (Thread 1: signal SIGABRT) when I type (in the search bar) in a letter which correspond to a letter in an list of names (var namen*) I made. When I type in a letter which not correspond to a letter in my list of names I don't get the error. I checked my IBoutlets but they were not the problem. Does someone know how to fix the error?
Here's my initial viewcontroller code: (some names are Dutch)
import UIKit
class elementstableviewcontroller: UITableViewController,
UISearchResultsUpdating{
#IBOutlet var tableview: UITableView!
var namen = ["Waterstof","Helium","Litium","Beryllium","Boor","Koolstof","Stikstof","Zuurstof","Fluor","Neon"]
var afkortingen = ["H","He","Li","Be","B","C","N","O","F","Ne"]
var atoommassas = ["Massa: 1,008","Massa: 4,003","Massa: 6,941","Massa: 9,012","Massa: 10,81","Massa: 12,01","Massa: 14,01","Massa: 16,00","Massa: 19,00","Massa: 20,18"]
var atoomnummers = ["Nummer: 1","Nummer: 2","Nummer: 3","Nummer: 4","Nummer: 5","Nummer: 6","Nummer: 7","Nummer: 8","Nummer: 9","Nummer: 10"]
var ladingen = ["Lading: +1,-1","Lading: 0","Lading: +1","Lading: +2","Lading: +3","Lading: +2,+4,-4","Lading: +1,+2,+3,+4,+5,-1,-2,-3","Lading: -2","Lading: -1","Lading: 0"]
var electronenconfig = ["Config: 1","Config: 2","Config: 2,1","Config: 2,2","Config: 2,3","Config: 2,4","Config: 2,5","Config: 2,6","Config: 2,7","Config: 2,8"]
var searchcontroller : UISearchController!
var resultscontroller = UITableViewController()
var filterednamen = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.resultscontroller.tableView.dataSource = self
self.resultscontroller.tableView.delegate = self
self.searchcontroller = UISearchController(searchResultsController: self.resultscontroller)
self.tableView.tableHeaderView = self.searchcontroller.searchBar
self.searchcontroller.searchResultsUpdater = self
self.searchcontroller.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
}
func updateSearchResults(for searchController: UISearchController) {
self.filterednamen = self.namen.filter { (naam:String) -> Bool in
if naam.lowercased().contains(self.searchcontroller.searchBar.text!.lowercased()) {
return true
}else{
return false
}
}
self.resultscontroller.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView{
return self.namen.count
}else {
return self.filterednamen.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.afkorting.text = afkortingen[indexPath.row]
cell.name.text = namen[indexPath.row]
cell.atoommassa.text = atoommassas[indexPath.row]
cell.elektronenconfiguratie.text = electronenconfig[indexPath.row]
cell.atoomnummer.text = atoomnummers[indexPath.row]
cell.lading.text = ladingen[indexPath.row]
if tableView == self.tableView{
//cell.textLabel?.text = self.namen[indexPath.row]
cell.name.text = self.namen[indexPath.row]
}else{
//cell.textLabel?.text = self.filterednamen[indexPath.row]
cell.name.text = self.filterednamen[indexPath.row]
}
return cell
}
}
And here's my CustomCell code:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet var elektronenconfiguratie: UILabel!
#IBOutlet var atoomnummer: UILabel!
#IBOutlet var atoommassa: UILabel!
#IBOutlet var name: UILabel!
#IBOutlet var afkorting: UILabel!
#IBOutlet var lading: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
*namen = names and naam = name translated from Dutch to English
Try to implement filter functionality in shouldChangeTextIn SearchBar method as given below
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
self.filterednamen = self.namen.filter { (naam:String) -> Bool in
return naam.lowercased().contains(self.searchcontroller.searchBar.text!.lowercased())
}
self.resultscontroller.tableView.reloadData()
}
There are several problems with your code.
Firstly, you are creating an IBOutlet of a UITableView unnecessarily. Remove that from your code.
Secondly, you are using that table view for checking if you are searching or not. That is unneccesary, as you want to show search results in the same view controller, so the correct boolean check for that instead is:
!searchcontroller.isActive || searchcontroller.searchBar.text == ""
Lastly, the error occurs exactly because the last issue stated: you are setting the searchResultsController to a phantom table view, so it gets an assertion failure, as it can't find a cell identified "cell".
Here is the fixed code:
import UIKit
class FirstViewController: UITableViewController,
UISearchResultsUpdating{
var namen = ["Waterstof","Helium","Litium","Beryllium","Boor","Koolstof","Stikstof","Zuurstof","Fluor","Neon"]
var afkortingen = ["H","He","Li","Be","B","C","N","O","F","Ne"]
var atoommassas = ["Massa: 1,008","Massa: 4,003","Massa: 6,941","Massa: 9,012","Massa: 10,81","Massa: 12,01","Massa: 14,01","Massa: 16,00","Massa: 19,00","Massa: 20,18"]
var atoomnummers = ["Nummer: 1","Nummer: 2","Nummer: 3","Nummer: 4","Nummer: 5","Nummer: 6","Nummer: 7","Nummer: 8","Nummer: 9","Nummer: 10"]
var ladingen = ["Lading: +1,-1","Lading: 0","Lading: +1","Lading: +2","Lading: +3","Lading: +2,+4,-4","Lading: +1,+2,+3,+4,+5,-1,-2,-3","Lading: -2","Lading: -1","Lading: 0"]
var electronenconfig = ["Config: 1","Config: 2","Config: 2,1","Config: 2,2","Config: 2,3","Config: 2,4","Config: 2,5","Config: 2,6","Config: 2,7","Config: 2,8"]
var searchcontroller : UISearchController!
var filterednamen = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchcontroller = UISearchController(searchResultsController: nil)
self.tableView.tableHeaderView = self.searchcontroller.searchBar
self.searchcontroller.searchResultsUpdater = self
self.searchcontroller.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
}
func updateSearchResults(for searchController: UISearchController) {
self.filterednamen = self.namen.filter { (naam:String) -> Bool in
if naam.lowercased().contains(self.searchcontroller.searchBar.text!.lowercased()) {
return true
}else{
return false
}
}
self.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if !searchcontroller.isActive || searchcontroller.searchBar.text == "" {
return self.namen.count
}else {
return self.filterednamen.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.afkorting.text = afkortingen[indexPath.row]
cell.name.text = namen[indexPath.row]
cell.atoommassa.text = atoommassas[indexPath.row]
cell.elektronenconfiguratie.text = electronenconfig[indexPath.row]
cell.atoomnummer.text = atoomnummers[indexPath.row]
cell.lading.text = ladingen[indexPath.row]
if !searchcontroller.isActive || searchcontroller.searchBar.text == "" {
//cell.textLabel?.text = self.namen[indexPath.row]
cell.name.text = self.namen[indexPath.row]
}else{
//cell.textLabel?.text = self.filterednamen[indexPath.row]
cell.name.text = self.filterednamen[indexPath.row]
}
return cell
}
}

How to implement a custom search-bar when the array type is JSON

I'm working on a project in swift 3 and I have a specific UIViewController where I have a UITableView and as to populate data on its cell, the data is get from the server and I assign it to an array of the type JSON. Thus, I have a UISearch bar placed at a different place in the same UIViewController(not as the header of the UITableView). My requirement is to implement the functionality of the search bar where i could filter and search the data of my UITableView. How can I achieve this? I worked on the code half way though which is not fucntioning and the code as bellow.
import UIKit
import SwiftyJSON
import SDWebImage
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchResultsUpdating,UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
var count : Int = 0
var searchActive : Bool?
var selectedCategoryList = [JSON?]()
var filterSelectedCategoryList = [JSON?]()
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
passJson()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.delegate = self
self.tableView.reloadData()
// Do any additional setup after loading the view, typically from a nib.
}
func updateSearchResults(for searchController: UISearchController) {
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterSelectedCategoryList = selectedCategoryList.filter { titles in
return (titles?["title"].stringValue.lowercased().contains(searchText.lowercased()))!
}
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func passJson() {
let path : String = Bundle.main.path(forResource: "JsonFile", ofType: "json") as String!
let jsonData = NSData(contentsOfFile: path) as NSData!
let readableJson = JSON(data: jsonData as! Data, options: JSONSerialization.ReadingOptions.mutableContainers, error: nil)
let json = readableJson
print(json)
let selectedItemArray = json["itemList"].arrayValue
self.selectedCategoryList = selectedItemArray
print("new prints",self.selectedCategoryList)
// print( "Count",selectedItemArray?.count as Any)
self.count = self.selectedCategoryList.count
print(self.count)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchController.isActive && searchController.searchBar.text != ""{
return filterSelectedCategoryList.count
}else{
return selectedCategoryList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
if count > 0 {
if self.searchController.isActive && searchController.searchBar.text != ""{
cell.label.text = filterSelectedCategoryList[indexPath.row]?["healerName"].stringValue
}else{
cell.label.text = selectedCategoryList[indexPath.row]?["healerName"].stringValue
}
}
return cell
}
}
Try to implement the UISearch​Bar​Delegate method but before that set the searchBar.delegate to self in your viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
passJson()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.sizeToFit()
//Set the delegate of searchBar
searchController.searchBar.delegate = self
self.tableView.reloadData()
}
Now implement the search​Bar(_:​text​Did​Change:​) method of UISearchBarDelegate and filter your array in that method.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterSelectedCategoryList = selectedCategoryList.filter { titles in
return (titles?["healerName"].stringValue.lowercased().contains(searchText.lowercased()))!
}
tableView.reloadData()
}

Adding search bar programmatically to tableview in swift

I have an textfield which represents an tableview as its inputview. I want to add 2 things to this tableview.
1) add a search bar.
2) add cancell button to top of tableview.
class enterYourDealVC: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UISearchResultsUpdating {
var tableView: UITableView = UITableView()
let searchController = UISearchController(searchResultsController: nil)
var dealAirports = [
airPorts(name: "Airport1", shortcut: "AP1")!),
airPorts(name: "Airport2", shortcut: "AP2")!)
]
var filteredAirports = [airPorts]()
//view did load
tableView = UITableView(frame: UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
toTextField.inputView = self.tableView
//here is my search function
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredAirports = dealAirports.filter { ap in
return ap.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
}
The problem is with this code, it doesn't search. Also when I click the search bar it dismiss the tableview and returns me back to viewcontroller. How can I fix this?
and how can I add cancel button to this tableview?
This will add a SeachBar
lazy var searchBar:UISearchBar = UISearchBar()
override func viewDidLoad()
{
searchBar.searchBarStyle = UISearchBar.Style.default
searchBar.placeholder = " Search..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
navigationItem.titleView = searchBar
}
func searchBar(_ searchBar: UISearchBar, textDidChange textSearched: String)
{
...your code...
}
In swift 4.1 and Xcode 9.4.1
Step1
Add UISearchBarDelegate to your view controller.
Step2
//Write this code in viewDidLoad() or your required function
let searchBar:UISearchBar = UISearchBar()
//IF you want frame replace first line and comment "searchBar.sizeToFit()"
//let searchBar:UISearchBar = UISearchBar(frame: CGRect(x: 10, y: 10, width: headerView.frame.width-20, height: headerView.frame.height-20))
searchBar.searchBarStyle = UISearchBarStyle.prominent
searchBar.placeholder = " Search..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
yourViewName.addSubview(searchBar)//Here change your view name
Step3
func searchBar(_ searchBar: UISearchBar, textDidChange textSearched: String) {
//your code here....
}
Swift 4+
class SearchViewController: UIViewController {
#IBOutlet weak var maintableView: UITableView!
#IBOutlet weak var searchUIBar: UISearchBar!
var isSearch : Bool = false
var tableData = ["Afghanistan", "Algeria", "Bahrain","Brazil", "Cuba", "Denmark","Denmark", "Georgia", "Hong Kong", "Iceland", "India", "Japan", "Kuwait", "Nepal"];
var filteredTableData:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
maintableView.dataSource = self
maintableView.delegate = self
searchUIBar.delegate = self
maintableView.reloadData()
}
}
extension SearchViewController: UISearchBarDelegate{
//MARK: UISearchbar delegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
isSearch = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.count == 0 {
isSearch = false
self.maintableView.reloadData()
} else {
filteredTableData = tableData.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filteredTableData.count == 0){
isSearch = false
} else {
isSearch = true
}
self.maintableView.reloadData()
}
}
}
extension SearchViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = maintableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if (isSearch) {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = tableData[indexPath.row]
print(tableData[indexPath.row])
return cell
}
}
}
extension SearchViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(isSearch) {
return filteredTableData.count
}else{
return tableData.count
}
}
}
Here is code snipet in swift for the same For more just refer Apple Doc mentioned in comment.
UISearchDisplayController is deprecated in IOS8.0, and recommended to use UISearchController
Hope this will help you alot
ContactTVC
class ContactTVC:UITableViewController{
// MARK: Types
/// State restoration values.
enum RestorationKeys : String {
case viewControllerTitle
case searchControllerIsActive
case searchBarText
case searchBarIsFirstResponder
}
struct SearchControllerRestorableState {
var wasActive = false
var wasFirstResponder = false
}
/*
The following 2 properties are set in viewDidLoad(),
They an implicitly unwrapped optional because they are used in many other places throughout this view controller
*/
/// Search controller to help us with filtering.
var searchController: UISearchController!
/// Secondary search results table view.
var resultsTableController: ResultsTableController!
/// Restoration state for UISearchController
var restoredState = SearchControllerRestorableState()
var arrayContacts: Array<CNContact> = []
var searchResultArrayContacts: Array<CNContact> = []
override func viewDidLoad() {
super.viewDidLoad()
resultsTableController = ResultsTableController()
// We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
resultsTableController.tableView.delegate = self
searchController = UISearchController(searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
searchController.dimsBackgroundDuringPresentation = false // default is YES
searchController.searchBar.delegate = self // so we can monitor text changes + others
/*
Search is now just presenting a view controller. As such, normal view controller
presentation semantics apply. Namely that presentation will walk up the view controller
hierarchy until it finds the root view controller or one that defines a presentation context.
*/
definesPresentationContext = true
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Restore the searchController's active state.
if restoredState.wasActive {
searchController.active = restoredState.wasActive
restoredState.wasActive = false
if restoredState.wasFirstResponder {
searchController.searchBar.becomeFirstResponder()
restoredState.wasFirstResponder = false
}
}
}
//MARK override TableViewDelegates/Datasource methods
}
extension ContactTVC: UISearchResultsUpdating{
// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
if let text = searchController.searchBar.text where (text.isEmpty == false){
{
// Hand over the filtered results to our search results table.
let resultsController = searchController.searchResultsController as! ResultsTableController
resultsController.filteredProducts = Array(searchResult)
resultsController.tableView.reloadData()
dispatch_async(dispatch_get_main_queue(), {
self.tableViewContacts.reloadData()
})
}
}
}
}
//MARK: SearchBarDelegate
extension ContactTVC: UISearchBarDelegate{
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if let text = searchBar.text where (text.isEmpty == false) {
// update the search result array by filtering….
if searchResult.count > 0{
self.searchResultArrayContacts = Array(searchResult)
}
else{
self.searchResultArrayContacts = Array(self.arrayContacts)
}
dispatch_async(dispatch_get_main_queue(), {
self.tableViewContacts.reloadData()
})
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
}
}
/// The table view controller responsible for displaying the filtered products as the user types in the search field.
class ResultsTableController: UITableViewController {
// MARK: Properties
let reusableIdentifier = "contactCell"
var filteredProducts = [CNContact]()
override func viewDidLoad() {
self.tableView.emptyDataSetSource = self
self.tableView.emptyDataSetDelegate = self
}
// MARK: UITableViewDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredProducts.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: reusableIdentifier)
var contact = CNContact()
contact = filteredProducts[indexPath.row]
// Configure the cell...
cell.textLabel?.text = contact.givenName
let phones = contact.phoneNumbers[0].value as! CNPhoneNumber
cell.detailTextLabel?.text = phones.stringValue
return cell
}
}
Thanks
//first write delegate for search "UISearchBarDelegate"
//MARK:- Search button action
#IBAction func searchWithAddress(_ sender: Any) {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.delegate = self
self.present(searchController, animated: true, completion: nil)
}

Resources