So I'm trying to add a SearchBar on top of my TableView so when someone searches something It will show that cell, I did some research, and all of the examples were using an array[String] and my array has a data model. If anyone could help me out with this. Thank you
Data Model
struct SurahData: Decodable {
let data: [Datum]
}
struct Datum: Decodable {
let number: Int
let englishName,englishNameTranslation: String
}
View Controller,
Each Cell has two labels
import UIKit
class SecondViewController: UIViewController {
var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var emptyArray = [Datum]()
var numberArray = [Int](1...114)
let gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
startIndicator()
activityIndicator.startAnimating()
activityIndicator.backgroundColor = .white
callingSurah()
gradientLayer.frame = view.bounds
gradientLayer.colors = [
UIColor.systemRed.cgColor,
UIColor.systemOrange.cgColor,
]
}
override func viewWillLayoutSubviews() {
gradientLayer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
}
func startIndicator() {
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicator.style = UIActivityIndicatorView.Style.large
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func callingSurah(){
let quranURL = "https://api.alquran.cloud/v1/surah"
guard let convertedURL = URL(string: quranURL) else {return}
URLSession.shared.dataTask(with: convertedURL) { data, response, error in
if let error = error {
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
print("An error has occured while performing the call \(String(describing: error))")
}
if let data = data {
do{
let decoder = try JSONDecoder().decode(SurahData.self, from: data)
DispatchQueue.main.async{
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
self.emptyArray = decoder.data
self.tableView.reloadData()
print(self.emptyArray)
}
}
catch{
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
print("Error occured while decoding \(String(describing: error))")
}
}
}
.resume()
}
}
extension SecondViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return emptyArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
UITableViewCell()}
cell.englishName.text = emptyArray[indexPath.row].englishName
cell.engTrans.text = emptyArray[indexPath.row].englishNameTranslation
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "AyahController") as? AyahController else {return}
vc.surahNumber = emptyArray[indexPath.row].number
navigationController?.pushViewController(vc, animated: true)
}
}
You need to handle search text with UISearchBarDelegate
Declare an Array which can store your search results:
var filteredArr: [Datum]?
Declare Bool to keep track if search is active:
var isSearchActive = false
Assign delegate in your viewDidLoad method
searchBar.delegate = self
Add delegate methods
extension ViewController: UISearchBarDelegate {
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
isSearchActive = true //Make search active
self.tableView.reloadData()
return true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let surahList = self.emptyArray else { return }
self.filteredArr = searchText.isEmpty ? surahList : surahList.filter {
($0.englishName).range(of: searchText, options: .caseInsensitive) != nil //Filter data with user input for englishName
}
self.tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearchActive = false //deactivate search
self.tableView.reloadData()
}
}
And you need to update your tableView delegate methods as well
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearchActive {
return filteredArr?.count ?? 0
} else {
return emptyArray?.count ?? 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
UITableViewCell()}
var product: Datum?
if isSearchActive {
product = self.filteredArr?[indexPath.row]
} else {
product = self.emptyArray?[indexPath.row]
}
cell.englishName.text = product?.englishName ?? ""
cell.engTrans.text = product?.englishNameTranslation ?? ""
return cell
}
}
Here is the full code and I made some minor update as well:
class ViewController: UIViewController {
var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var emptyArray: [Datum]? //Declare it as optional
let gradientLayer = CAGradientLayer()
var filteredArr: [Datum]?
var isSearchActive = false
override func viewDidLoad() {
super.viewDidLoad()
startIndicator()
activityIndicator.startAnimating()
activityIndicator.backgroundColor = .white
callingSurah()
gradientLayer.frame = view.bounds
gradientLayer.colors = [
UIColor.systemRed.cgColor,
UIColor.systemOrange.cgColor,
]
searchBar.delegate = self
}
override func viewWillLayoutSubviews() {
gradientLayer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
}
func startIndicator() {
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicator.style = UIActivityIndicatorView.Style.large
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func callingSurah(){
let quranURL = "https://api.alquran.cloud/v1/surah"
guard let convertedURL = URL(string: quranURL) else {return}
URLSession.shared.dataTask(with: convertedURL) { data, response, error in
if let error = error {
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
print("An error has occured while performing the call \(String(describing: error))")
}
if let data = data {
do{
let decoder = try JSONDecoder().decode(SurahData.self, from: data)
DispatchQueue.main.async{
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
self.emptyArray = decoder.data
self.tableView.reloadData()
}
}
catch{
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
print("Error occured while decoding \(String(describing: error))")
}
}
}
.resume()
}
}
extension ViewController: UISearchBarDelegate {
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
isSearchActive = true
self.tableView.reloadData()
return true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let surahList = self.emptyArray else { return }
self.filteredArr = searchText.isEmpty ? surahList : surahList.filter {
($0.englishName).range(of: searchText, options: .caseInsensitive) != nil
}
self.tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearchActive = false
self.tableView.reloadData()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearchActive {
return filteredArr?.count ?? 0
} else {
return emptyArray?.count ?? 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
UITableViewCell()}
var product: Datum?
if isSearchActive {
product = self.filteredArr?[indexPath.row]
} else {
product = self.emptyArray?[indexPath.row]
}
cell.englishName.text = product?.englishName ?? ""
cell.engTrans.text = product?.englishNameTranslation ?? ""
return cell
}
}
Related
I have been facing issue after searching of tableview. PDF files stored in Firebase are listed in alphabetic order in tableview and can be opened invidually in detailed view as well as after search an item , filtered item can be also viewed without any problem. However, after back to list and click same filtered pdf without refreshing the list, it gives me the first pdf of the list which is that the indexpath.row is zero.
For example, when I search and click on the item with indexpath.row number 3, I reach the relevant pdf file. But when I come back and click on the same filtered item, it brings up the first item of the whole list. Cant figure out how to handle it. Thank you.
import UIKit
import Firebase
import PDFKit
class IcsViewcontroller: UIViewController , UISearchBarDelegate {
var preImage : UIImage?
let cellSpacingHeight: CGFloat = 20
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var pdfListView: UITableView!
#IBOutlet weak var spinner: UIActivityIndicatorView!
var pdfList = [pdfClass]()
var searchall = [pdfClass]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
pdfListView.delegate = self
pdfListView.dataSource = self
searchBar.delegate = self
self.pdfListView.isHidden = true
getPdf()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if searching {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = searchall[selectedIndexPath!.row]
} else {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = pdfList [selectedIndexPath!.row]
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
} else {
searching = true
}
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searching = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
self.pdfListView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searchall = pdfList
searching = false
pdfListView.reloadData()
} else {
searching = true
searchall = pdfList.filter({($0.pdf_name?.lowercased().prefix(searchText.count))! == searchText.lowercased() })
pdfListView.reloadData()
}
}
func getPdf () {
spinner.startAnimating()
let docRef = Storage.storage().reference().child("ICS_Documents")
docRef.listAll{ (result , error ) in
if let error = error {
print (error )
}
for item in result.items {
let storeageLocation = String( describing : item)
let gsReference = Storage.storage().reference(forURL: storeageLocation)
gsReference.downloadURL{ url , error in
//self.pdfList.removeAll()
if let error = error{
print(error)
} else {
let pdf_name = String( item.name)
let pdf_url = url?.absoluteString
let thumbnailSize = CGSize(width: 100, height: 100)
let thmbnail = self.generatePdfThumbnail(of: thumbnailSize, for: url!, atPage: 0)
let pdfall = pdfClass(pdf_name: pdf_name, pdf_url: pdf_url!, pdf_preview: thmbnail!)
self.pdfList.append(pdfall)
}
DispatchQueue.main.async {
self.pdfList = self.pdfList.sorted(by: { $0.pdf_name ?? "" < $1.pdf_name ?? ""})
self.pdfListView.reloadData()
self.spinner.stopAnimating()
self.pdfListView.isHidden = false
}
}
}
}
}
func generatePdfThumbnail(of thumbnailSize: CGSize , for documentUrl: URL, atPage pageIndex: Int) -> UIImage? {
let pdfDocument = PDFDocument(url: documentUrl)
let pdfDocumentPage = pdfDocument?.page(at: pageIndex)
return pdfDocumentPage?.thumbnail(of: thumbnailSize, for: PDFDisplayBox.trimBox)
}
}
extension IcsViewcontroller : UITableViewDelegate,UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching{
return searchall.count
}else {
return pdfList.count
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "pdfCell", for: indexPath) as! pdfCellTableViewCell
let varcell : pdfClass
if searching {
varcell = searchall [indexPath.row]
} else {
varcell = pdfList [indexPath.row]
}
cell.configure(name: varcell.pdf_name! , pdfthumbnail: varcell.pdf_preview!)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var indx : pdfClass
if searching{
indx = searchall[indexPath.row ]
print(indexPath.row )
}else {
indx = pdfList[indexPath.row]
}
performSegue(withIdentifier: "toPdfKit", sender: indx)
print(indexPath.row)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action:UITableViewRowAction,indexPath:IndexPath)-> Void in
let storage = Storage.storage()
let childsRURL = self.pdfList[indexPath.row].pdf_url
let storageref = storage.reference(forURL: childsRURL!)
storageref.delete{ error in
if let error = error {
print(error.localizedDescription)
} else{
print("File deleted")
}
}
self.pdfListView.reloadData()
})
return [deleteAction]
}
}
This is the pdfClass
import Foundation
import UIKit
class pdfClass : NSObject {
var pdf_name : String?
var pdf_url : String?
var pdf_preview : UIImage?
override init(){
}
init (pdf_name :String , pdf_url : String, pdf_preview : UIImage ) {
self.pdf_name = pdf_name
self.pdf_url = pdf_url
self.pdf_preview = pdf_preview
}
}
I believe your problem is here, when you click on the cell, your searchBar editing is finished and you make the variable false, changing the list you are working on in the delegate.
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searching = false
}
For simplicity, I suggest keeping the original list just to load the results into a helper list that is used throughout the class, rather than working with two lists in each delegate.
Like this way:
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()
}
}
I have a problem with the search bar, when I load the app the Json data is loaded correctly in the table view, but if I enter a word in the search field nothing happens, the data is not searched and the tableView remains the same.
ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var popularMoviesArray = [Results]()
var swiftManager = SwiftManager()
var tableViewCell = TableViewCell()
#IBOutlet weak var tableView: UITableView!
// START SEARCHBAR
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var labelError: UILabel!
var filteredMoviesArray = [Results]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
// END SEARCHBAR
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
swiftManager.delegate = self
// START SEARCHBAR
searchBar.delegate = self
searchBar.placeholder = "Search here..."
// END SEARCHBAR
swiftManager.fetchUrl()
}
// START SEARCHBAR
func rowOk() {
labelError.backgroundColor = UIColor.white
labelError.text = ""
}
func rowError() {
labelError.textColor = UIColor.white
labelError.backgroundColor = UIColor.red
labelError.textAlignment = .center
labelError.text = "no records found"
}
// END SEARCHBAR
// MARK: - TableView Datasource Methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// START SEARCHBAR
if filteredMoviesArray.count != 0 {
rowOk()
print("OK - \(filteredMoviesArray.count)")
return filteredMoviesArray.count
} else {
rowError()
print("ERROR - \(filteredMoviesArray.count)")
return filteredMoviesArray.count
}
// END SEARCHBAR
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = filteredMoviesArray[indexPath.row]
cell.labelTitle.text = item.title
cell.labelYear.text = labelYearFormatter
cell.labelRate.text = String(item.vote_average ?? 0.0)
cell.labelOreview.text = item.overview
return cell
}
// MARK: - TableView Delegate Methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToDetail", sender: indexPath.row)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier != nil else {
return
}
let letRow = sender as? Int
switch segue.identifier {
case "goToDetail":
(segue.destination as! ViewControllerDetail).itemDetail = filteredMoviesArray[letRow!]
default:
return
}
}
}
extension SwiftManagerDelegate
//MARK: - SwiftManagerDelegate
extension ViewController: SwiftManagerDelegate {
func didUpdateStruct(_ swiftManager: SwiftManager, swiftData: SwiftData) {
DispatchQueue.main.async {
self.filteredMoviesArray = swiftData.results
self.tableView.reloadData()
}
}
func didFailWithError(error: Error) {
print(error)
}
}
extension UISearchBarDelegate
//MARK: - UISearchBarDelegate
// START SEARCHBAR
extension ViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard
let searchText = searchBar.text
else {
return
}
if searchText.isEmpty == true {
func didUpdateStruct(_ swiftManager: SwiftManager, swiftData: SwiftData) {
DispatchQueue.main.async {
self.filteredMoviesArray = swiftData.results
self.tableView.reloadData()
}
}
print("EMPTY")
return
} else {
func didUpdateStruct(_ swiftManager: SwiftManager, swiftData: SwiftData) {
DispatchQueue.main.async {
self.filteredMoviesArray = swiftData.results.filter {
$0.title!.uppercased().contains(searchText.uppercased())
}
self.tableView.reloadData()
}
}
print("FULL")
return
}
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
swiftManager.fetchUrl()
}
}
// END SEARCHBAR
as per title, UISearchBar is not responding in UITableView with JSON data. I can't get the search field to work, can you help me please?
The TableView works fine, the data displays it, but when I enter a word in the search field nothing happens.
Maybe the problem could lie within this extension?
extension ViewController: UISearchBarDelegate
import UIKit
struct GalleryData: Decodable {
let localized_name: String
let primary_attr: String
let attack_type: String
let img: String
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataArray = [GalleryData]()
var filteredArray = [String]()
var shouldShowSearchResults = false
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
searchBar.placeholder = "Search here..."
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {
return filteredArray.count
} else {
return dataArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
if shouldShowSearchResults {
cell.textLabel?.text = filteredArray[indexPath.row]
}
else {
cell.textLabel?.text = dataArray[indexPath.row].localized_name.capitalized
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController {
destination.galleryDataDetail = dataArray[(tableView.indexPathForSelectedRow?.row)!]
}
}
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://api.opendota.com/api/heroStats")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.dataArray = try JSONDecoder().decode([GalleryData].self, from: data!)
DispatchQueue.main.async {
completed()
}
}
catch {
print("JSON error")
}
}.resume()
}
}
extension ViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let searchString = searchBar.text
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country as NSString
return (countryText.range(of: searchString!, options: .caseInsensitive).location) != NSNotFound
})
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
shouldShowSearchResults = false
tableView.reloadData()
}
}
Try to change your code like this
import UIKit
struct GalleryData: Decodable
{
let localized_name: String
let primary_attr: String
let attack_type: String
let img: String
}
class ViewController: UIViewController
{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var dataArray = [GalleryData]()
var filteredArray = [GalleryData]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
searchBar.placeholder = "Search here..."
downloadJSON()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = filteredArray[indexPath.row].localized_name.capitalized
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController {
destination.galleryDataDetail = filteredArray[(tableView.indexPathForSelectedRow?.row)!]
}
}
func downloadJSON() {
let url = URL(string: "https://api.opendota.com/api/heroStats")
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
do {
self.dataArray = try JSONDecoder().decode([GalleryData].self, from: data!)
self.filteredArray = self.dataArray
}
catch {
print("JSON error")
}
}.resume()
}
}
extension ViewController: UISearchBarDelegate
{
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchBar.text else { return }
if (searchText == "") {
filteredArray = dataArray
return
}
filteredArray = dataArray.filter { $0.localized_name.uppercased.contains(searchText.uppercased) }
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
}
}
First of all it's more efficient to declare the filtered array with the same type as the data source array
var dataArray = [GalleryData]()
var filteredArray = [GalleryData]()
In textDidChange check if the search string is empty and set shouldShowSearchResults accordingly.
And the bridge cast to NSString is not needed at all
extension ViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
shouldShowSearchResults = false
filteredArray = []
} else {
filteredArray = dataArray.filter{ $0.localized_name.range(of: searchText, options: .caseInsensitive) != nil }
shouldShowSearchResults = true
}
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
shouldShowSearchResults = false
tableView.reloadData()
}
}
searchBarTextDidBeginEditing is not needed either.
In cellForRowAt add an identifier to the cell and reuse the cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GalleryCell", for: indexPath)
let item = shouldShowSearchResults ? filteredArray[indexPath.row] : dataArray[indexPath.row]
cell.textLabel?.text = item.localized_name.capitalized
return cell
}
And be aware that the segue doesn't work (can even crash) if the search results are displayed
A more sophisticated solution is UITableViewDiffableDataSource (iOS 13+)
The Delegated function fires but crashes as its nil, the objects in the items array is populated by CoreData, this var model: CoreDataModel = CoreDataModel(CoreDataController.shared) has to be instantiated rather than as expected in the viewDidLoad to prevent a nil error for the table view row count (model.items.count)
On startup the items array is the complete Sqlite DB, on search its the subset of the table and printing to the console proves the array is changed and only has the subset of Albums.
BaseViewController
import UIKit
import CoreData
protocol UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}
protocol MasterModel {
var client: LastFMClient { get }
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void)
}
protocol DataReloadTableViewDelegate: class {
func reloadAlbumsTable()
}
class BaseViewController: UITableViewController, MasterModel {
let cellId = "sdlfjowieurewfn3489844224947824dslaksjfs;ad"
let logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let image = UIImage(named: "lastFMRedBlack")
let searchBar = UISearchBar()
let client = LastFMClient()
var model: CoreDataModel = CoreDataModel(CoreDataController.shared)
private var searchResults: Root?
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellId)
tableView.tableFooterView = UIView(frame: CGRect.zero)
tableView.separatorColor = UIColor(red: 72.5/255, green: 0/255, blue: 0/255, alpha: 1)
imageView.contentMode = .scaleAspectFit
imageView.image = image
logoContainer.addSubview(imageView)
navigationItem.titleView = logoContainer
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
model.delegate = self
model.fetchAllAlbums()
}
// MARK - SearchBar
private func setupSearchController() {
searchBar.sizeToFit()
searchBar.placeholder = "Search for Album"
searchBar.delegate = self
showSearchBarButton(shouldShow: true)
}
func showSearchBarButton (shouldShow: Bool) {
if shouldShow {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(handleShowSearchBar))
} else {
searchBar.showsCancelButton = true
navigationItem.rightBarButtonItem = nil
}
}
func search(shouldShow: Bool) {
showSearchBarButton(shouldShow: !shouldShow)
navigationItem.titleView = shouldShow ? searchBar : logoContainer
}
#objc func handleShowSearchBar(){
search(shouldShow: true)
searchBar.becomeFirstResponder()
}
// MARK - API Request
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void) {
// Use the API to get data
client.getFeed(from: LastFMRequest.albumSearch(userSearchTerm: userSearchTerm) ) { result in
switch result {
case .success(let data):
do {
let data = try DataParser.parse(data, type: RootNode.self)
self.searchResults = data.results
completion(true)
} catch {
print(error.localizedDescription)
completion(false)
}
case .failure(let error):
print(error.localizedDescription)
completion(false)
}
}
}
}
extension BaseViewController: UISearchBarDelegate {
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.text = nil
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
search(shouldShow: false)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let searchTextString = searchBar.text else { return }
searchFeed(with: searchTextString.replacingOccurrences(of: " ", with: "+").lowercased(), completion: {_ in
if self.searchResults!.albumMatches.album.count == 0 {
DispatchQueue.main.async {
let alertController = UIAlertController(title: "No Albums Found", message: "Try Another Keyword(s)", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { action in
print("Pressed OK")
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
} else {
let dataManager = DataManager(data: self.searchResults!)
do {
try dataManager.saveData()
} catch {
print(error)
}
}
})
search(shouldShow: false)
searchBar.resignFirstResponder()
}
}
class SubtitleTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BaseViewController: UITableViewDataSource {
var numberOrSections: Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section >= 0 && section < numberOrSections else { return 0 }
return model.items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let albumItem = model.items[indexPath.row]
cell.textLabel?.text = albumItem.value(forKeyPath: "name") as? String
cell.detailTextLabel?.text = albumItem.value(forKeyPath: "artist") as? String
cell.accessoryType = .disclosureIndicator
// Populate the cell from the object
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = DetailViewController()
let albumItem = model.items[indexPath.row]
vc.iamgeURL = albumItem.value(forKeyPath: "imageUrl") as? String
vc.albumName = albumItem.value(forKeyPath: "name") as? String
navigationController?.pushViewController(vc, animated: true)
}
}
extension BaseViewController: DataReloadTableViewDelegate {
func reloadAlbumsTable(){
DispatchQueue.main.async {
print(self.model.items.count)
self.tableView.reloadData()
}
}
}
CoreDataModel
import Foundation
import CoreData
class CoreDataModel {
weak var delegate: DataReloadTableViewDelegate?
let coreDataController: CoreDataController
var items:[Albums] = []
init(_ coreDataController: CoreDataController) {
self.coreDataController = coreDataController
self.coreDataController.mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
internal func saveSearchAlbums(responseData: Root) throws {
let newSearch = Searches(context: coreDataController.mainContext)
newSearch.searchQuery = responseData.attr.forField
for (_, element) in responseData.albumMatches.album.enumerated() {
let newAlbum = Albums(context: coreDataController.mainContext)
let artistName = element.artist
let albumName = element.name
let imageUrlTwo = element.image[2].text
let imageUrlZero = element.image[0].text
let imageUrlOne = element.image[1].text
var imageUrl: String = ""
if !JustLetters.blank(text: imageUrlTwo) {
imageUrl = imageUrlTwo
}
if !JustLetters.blank(text: imageUrlZero) {
imageUrl = imageUrlZero
}
if !JustLetters.blank(text: imageUrlOne) {
imageUrl = imageUrlOne
}
if !JustLetters.blank(text: artistName) && !JustLetters.blank(text: albumName) && !JustLetters.blank(text: imageUrl) {
newAlbum.searches = newSearch
newAlbum.artist = artistName
newAlbum.name = albumName
newAlbum.imageUrl = imageUrl
newSearch.addToAlbums(newAlbum)
}
}
// Save context
coreDataController.saveContext()
fetchAlbumsByKeyword(searchTerm: responseData.attr.forField)
}
internal func fetchAlbumsByKeyword(searchTerm: String) {
// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Albums")
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Add Predicate
let predicate = NSPredicate(format: "name CONTAINS[c] %#", searchTerm)
fetchRequest.predicate = predicate
do {
items = try coreDataController.mainContext.fetch(fetchRequest) as! [Albums]
} catch {
print(error)
}
delegate!.reloadAlbumsTable()
}
internal func fetchAllAlbums() {
// Create the FetchRequest for all searches
let allAlbums: NSFetchRequest = Albums.fetchRequest()
do {
items = try coreDataController.mainContext.fetch(allAlbums)
} catch {
print(error)
}
}
}
Delegate is assigned/set on the class name and not on any instance identifier so a delegate can only be set on a class with one instance
I am unable to show specific proof, I rely on cause and effect of a single change to make the above statement.
I had more than one instance of CoreDataModel, I set the delegate on the first instance in the viewDidLoad, the second instance is set on the search click. I refactored out the DataManager Class which itself creates and instance of CoreDataModel.
The final result is the delegate is not nil and performs as expected. Repo 'show_album_search_results' branch
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
}