Fatal Error: Index out of range, when using search bar Swift 3 - ios

I'm having a bit of an issue when trying to configure a search bar on a Table View. my data in the Table View loads fine, and if I make a selection segues to the next screen with the correct information. However on the Table View screen, as soon as I type a letter in the search bar, the app crashes with an Index out of range error.
Its a bit strange as previously I had this configured as a collection view, and the search bar worked fine.
Here is my code:
import UIKit
import GoogleMaps
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var TR: Tracks!
var track = [Tracks]()
var filteredTrack = [Tracks]()
var inSearchMode = false
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
parseTrackCSV()
}
func parseTrackCSV() {
let path = Bundle.main.path(forResource: "tracks", ofType: "csv")!
do {
let csv = try CSV(contentsOfURL: path)
let rows = csv.rows
for row in rows {
let trackId = Int(row["id"]!)!
let name = row["name"]!
let postcode = row["postcode"]!
let trackType = row["type"]!
let locID = row["locID"]!
let lon = Double(row["long"]!)!
let lat = Double(row["lat"]!)!
let tr = Tracks(name: name, trackId: trackId, postcode: postcode, trackType: trackType, locId: locID, lon: lon, lat: lat)
track.append(tr)
}
} catch let err as NSError {
print(err.debugDescription)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return track.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "TrackCell", for: indexPath) as? TrackCell {
let tr: Tracks!
if inSearchMode {
tr = filteredTrack[indexPath.row]
cell.configureCell(track: tr)
} else {
tr = track[indexPath.row]
cell.configureCell(track: tr)
}
cell.configureCell(track: tr)
return cell
} else {
return UITableViewCell()
}
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
view.endEditing(true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
tableView.reloadData()
view.endEditing(true)
} else {
inSearchMode = true
let lower = searchBar.text!
filteredTrack = track.filter({$0.name.range(of: lower) != nil})
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var tr: Tracks!
if inSearchMode {
tr = filteredTrack[indexPath.row]
} else {
tr = track[indexPath.row]
}
performSegue(withIdentifier: "TrackDetailVC", sender: tr)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TrackDetailVC" {
if let detailsVC = segue.destination as? TrackDetailVC {
if let tr = sender as? Tracks {
detailsVC.track = tr
}
}
}
}
}
The line that the break happens on is:
tr = filteredTrack[indexPath.row]

You are assigning number of rows using track.count and trying to get value from filteredTrack, when your search is enabled. So obvious you must get the same error.
You need to set condition here
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return filteredTrack.count
} else {
return track.count
}
}

Related

Indexpath row is zero after searching

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()
}
}

table view does not reload after delete letter in search bar

I am trying to search contact in my app. I am using search bar to do that.
Lets suppose that I have a 2 contacts, Tolga and Toygun. When I type for "To" in searchbar both contact appears in table view. Then I type for "Toy" in searchbar no one appears in table view as should be. The problem is when I delete the letter y in "Toy" no one continues to appear. I want to see both contact in table view when I delete letter y but I couldn't.
Here is my code:
class ContactsVC: UIViewController {
//MARK: - Proporties
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var emptyView: UIView!
let fireStoreDatabase = Firestore.firestore()
var contactArray = [Contact]()
var tempContactArray = [Contact]()
var letters: [Character] = []
var tempLetters: [Character] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
hideKeyboardWhenTappedAround()
getDataFromFirebase()
}
//MARK: - Function to Get Contacts Data From Firebase
func getDataFromFirebase(){
fireStoreDatabase.collection("Contacts").order(by: "contactName").addSnapshotListener { (snapshot, err) in
if err == nil {
if snapshot?.isEmpty == false && snapshot != nil {
self.contactArray.removeAll(keepingCapacity: false)
for document in snapshot!.documents {
if let uid = document.get("uid") as? String {
if uid == self.userId {
if let contactUrl = document.get("contactUrl") as? String,
let contactName = document.get("contactName") as? String,
let contactSirname = document.get("contactSirname") as? String,
let contactPhone = document.get("contactPhone") as? String,
let contactEmail = document.get("contactEmail") as? String,
let contactBloodgroup = document.get("contactBloodGroup") as? String,
let contactBirthday = document.get("contactBirthday") as? String{
self.contactArray.append(Contact(contactUrl: contactUrl, contactName: contactName, contactSirname: contactSirname, contactPhone: contactPhone, contactEmail: contactEmail, contactBloodgroup: contactBloodgroup, contactBirthday: contactBirthday, documentId: document.documentID))
}
}
}
}
self.tempContactArray = self.contactArray
//Section
self.letters.removeAll(keepingCapacity: false)
self.letters = self.contactArray.map({ (contact) in
return contact.contactName.uppercased().first!
})
self.letters = self.letters.sorted()
self.letters = self.letters.reduce([], { (list, name) -> [Character] in
if !list.contains(name) {
return list + [name]
}
return list
})
self.tempLetters = self.letters
self.tableView.reloadData()
} else {
self.contactArray.removeAll(keepingCapacity: false)
self.tableView.reloadData()
}
if(self.contactArray.count == 0) {
self.emptyView.isHidden = false
self.tableView.isHidden = true
}else{
self.emptyView.isHidden = true
self.tableView.isHidden = false
}
}
}
}
//MARK: - Section after search
func getLetters(contact: [Contact]) {
letters.removeAll(keepingCapacity: false)
letters = contact.map({ (contact) in
return contact.contactName.uppercased().first!
})
letters = letters.sorted()
letters = letters.reduce([], { (list, name) -> [Character] in
if !list.contains(name) {
return list + [name]
}
return list
})
}
//MARK: - Table View Data Source
extension ContactsVC: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
letters.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return letters[section].description
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ContactsViewCell
if letters[indexPath.section] == contactArray[indexPath.row].contactName.uppercased().first {
cell.contactImage.sd_setImage(with: URL(string: contactArray[indexPath.row].contactUrl))
cell.contactFullNameLabel.text = contactArray[indexPath.row].contactName + " " + contactArray[indexPath.row].contactSirname
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if letters[indexPath.section] == contactArray[indexPath.row].contactName.uppercased().first {
return 100.0
} else {
return 0.0
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "AddContactVC") as! AddContactVC
vc.isNewContact = false
vc.documentId = contactArray[indexPath.row].documentId
vc.contact = contactArray[indexPath.row]
self.present(vc, animated: true, completion: nil)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
view.endEditing(true)
}
}
//MARK: - Search Bar
extension ContactsVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print(searchText)
letters.removeAll(keepingCapacity: false)
if searchText.isEmpty == false {
contactArray = contactArray.filter{$0.contactName.lowercased().contains(searchText.lowercased())}
getLetters(contact: contactArray)
} else {
contactArray = tempContactArray
letters = tempLetters
}
self.tableView.reloadData()
}
}
This line causes the problem.
contactArray = contactArray.filter{$0.contactName.lowercased().contains(searchText.lowercased())}
let's consider the same example you mentioned. You have two contacts 'Tolgo' and 'Toygun'. When you type 'To', You filter the contacts and again assign it to the contactArray. So now contactArray will have two contacts Tolgo and Toygun. When you type 'Toy', again you apply filter on those 2 contacts in contactArray and assign to contactArray again. Now you will have only one contact detail 'Toygun' in contactArray. You are deleting 'y' from 'toy' search keyword, now you apply filter on contactArray which only has one contact(toygun). This causes only one contact to show in table
Solution:
Have all your fetched contacts in contactArray. On searching, filter from this array and assign the filtered items to tempContactArray. Have tempContactArray as the source array.
I hope i am able to help you solve your problem.
You can also implement UISearchController UISearchResultsUpdating protocol with function updateSearchResults and handle all your changes there. Here is a smooth tutorial: https://www.raywenderlich.com/4363809-uisearchcontroller-tutorial-getting-started

The search bar does not search for words entered in the search field

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

UISearchBar is not responding in UITableView with JSON data

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+)

How do I call an array for Searchbar for Swift Xcode

How do I get the correct viewcontroller after selecting a tableview cell can you please help me I think there is a problem with my index
import UIKit
var NamesC = [ "one", "two"]
var IndexC = 0
class ComputerVC: UIViewController ,UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var TableView: UITableView!
var data = [ "one", "two"]
var filteredData = [String]()
var inSearchMode = false
override func viewDidLoad() {
super.viewDidLoad()
TableView.delegate = self
TableView.dataSource = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return filteredData.count
}
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? DataCellC {
let text: String!
if inSearchMode {
text = filteredData[indexPath.row]
} else {
text = data[indexPath.row]
}
cell.congigureCell(text: text)
return cell
} else {
return UITableViewCell()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
view.endEditing(true)
TableView.reloadData()
} else {
inSearchMode = true
filteredData = data.filter({$0.contains(searchBar.text!)})
TableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
IndexC = indexPath.row
performSegue(withIdentifier: "segue", sender: self)
}
}
You can try
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
var text = ""
if inSearchMode {
text = filteredData[indexPath.row]
let correctIndex = data.index(of:filteredData[indexPath.row])
print("selected value is : \(correctIndex)")
} else {
text = data[indexPath.row]
}
performSegue(withIdentifier: "segue", sender: text)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondController = segue.destination as? SecondController {
secondController.sendedValue = sender as! String
}
}
class secondController:UIViewController
{
var sendedValue:String?
}
Issue is here:
Name.text = NamesC[IndexC]
Your array is filtered, your indexC is the one you used in the filtered array.
You can define a filtered array FNamesC, and update this array when you search in your search bar and in your viewController
Name.text = FNamesC[IndexC]
I don't know why you want a global array though.

Resources