Indexpath row is zero after searching - ios

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

Related

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

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 to insert data from string to Sqlite in Swift3?

I have a problem that I have tried to insert string data to Sqlite. I want to do is in my Details ViewController if user press Like button, the word and meaning of string pass into Sqlite file that show in Favourite ViewController. Below is my DetailsVC code.
import UIKit
import AVFoundation
class DetailsVC: UIViewController, UITextViewDelegate {
#IBOutlet weak var wordLbl: UILabel!
#IBOutlet weak var meaningLbl: UITextView!
#IBOutlet weak var sysWordLbl: UILabel!
#IBOutlet weak var sysWordsLbl: UILabel!
#IBOutlet weak var anyWordsLbl: UILabel!
#IBOutlet weak var anyWordLbl: UILabel!
#IBOutlet weak var wordImg: UIImageView!
#IBOutlet weak var buSound: UIButton!
#IBOutlet weak var buLike: UIButton!
#IBOutlet weak var menuBtn: UIButton!
//Data from HomeVC
var data:Data?
var fdatas = [FData]()
var favouriteVC = FavouriteVC()
override func viewDidLoad() {
super.viewDidLoad()
meaningLbl.delegate = self
wordLbl.text = "\((data?._word.capitalized)!) \((data?._phonetic)!)"
self.meaningLbl.text = data?._meaning
self.sysWordsLbl.text = data?._meaning
self.anyWordsLbl.text = data?._meaning
self.sysWordLbl.text = "Synonym"
self.anyWordLbl.text = "Antonym"
meaningLbl.font = UIFont(name: FONT_REGULAR, size: 24.0)
}
#IBAction func buMenu(_ sender: Any) {
//menu
dismiss(animated: true, completion: nil)
}
#IBAction func buSound(_ sender: Any) {
//Todo: speak the word
if buSound.isEnabled == true{
buSound.setImage(UIImage(named: "volume-2.png"), for: .normal)
}else{
buSound.setImage(UIImage(named: "volume-un-2.png"), for: .normal)
}
let utTerance = AVSpeechUtterance(string: "\((data?._word)!)")
let synthesize = AVSpeechSynthesizer()
utTerance.voice = AVSpeechSynthesisVoice(language: "en-gb")
synthesize.speak(utTerance)
}
#IBAction func buLike(_ sender: Any) {
//Todo: Click Like
if buLike.isEnabled == true{
buLike.setImage(UIImage(named: "heart.png"), for: .normal)
//Save data to Database
let word = data?._word ?? ""
let meaning = data?._meaning ?? ""
do{
let favData = FData(word: word, meaning: meaning)
self.fdatas.append(favData)
print("\(word), \(meaning)")
}catch{
print(error)
}
}else{
buSound.setImage(UIImage(named: "heart-un.png"), for: .normal)
}
//FavouriteVC.insertRowsAtIndexPaths([NSIndexPath(forRow: fdatas.count-1, inSection: 0)], withRowAnimation: .Fade)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
self.view.endEditing(true);
return false;
}
}
My FavouriteVC
import UIKit
import SwipeCellKit
import SQLite
class FavouriteVC: UIViewController,UITableViewDelegate,UITableViewDataSource, SwipeTableViewCellDelegate{
//TabelView
#IBOutlet weak var fTableView: UITableView!
//Variables
var fdata = [FData]()
override func viewDidLoad() {
super.viewDidLoad()
// FtableView
fTableView.delegate = self
fTableView.dataSource = self
//reload Data
fTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fdata.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: FAVOURITE_CELL, for: indexPath) as? FavouriteCell{
cell.configureCell(fdata: fdata[indexPath.row])
//cell.delegate = self
return cell
}
return FavouriteCell()
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete")
return [deleteAction]
}
}
FData.swif
import Foundation
class FData {
//let id: Int64?
var word: String
var meaning: String
//var address: String
init(id: Int64) {
// self.id = id
word = ""
meaning = ""
//address = ""
}
init(word: String, meaning: String) {
// self.id = id
self.word = word
self.meaning = meaning
//self.address = address
}
}
FavouriteData Instance
import UIKit
import SQLite
class FavouriteData{
static let instance = FavouriteData()
private let db: Connection?
private let words = Table("words")
private let id = Expression<Int64>("id")
private let wordL = Expression<String?>("word")
private let meaningL = Expression<String?>("shanword_uni")
//private let address = Expression<String>("address")
private init() {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
do {
db = try Connection("\(path)/favourite.sqlite")
} catch {
db = nil
print ("Unable to open database")
}
createTable()
}
func createTable() {
do {
try db!.run(words.create(ifNotExists: true) { table in
table.column(id, primaryKey: true)
table.column(wordL)
//table.column(phone, unique: true)
table.column(meaningL)
})
} catch {
print("Unable to create table")
}
}
func addData(word: String, meaning: String) -> Int64? {
do {
let insert = words.insert(wordL <- word, meaningL <- meaning)
let id = try db!.run(insert)
print("Save: \(id)")
return id
} catch {
print("Insert failed")
return -1
}
}
func getData() -> [FData] {
var contacts = [FData]()
do {
for contact in try db!.prepare(self.words) {
contacts.append(FData(
// id: contact[id],
word: contact[wordL]!,
meaning: contact[meaningL]!))
}
} catch {
print("Select failed")
}
return contacts
}
func deleteData(index: Int64) -> Bool {
do {
let contact = words.filter(id == index)
try db!.run(contact.delete())
return true
} catch {
print("Delete failed")
}
return false
}
}
But, there is not cell of row in the FavouriteVC. How to fix this? Please help me.
Best,
Sai Tawng Pha
I have solved my problem by add one line of code. In my FavouriteVC class, just add it under ViewdidLoad function " fdata = FavouriteData.instance.getData() ".
Here is my complete code.
import UIKit
import SwipeCellKit
import SQLite
class FavouriteVC: UIViewController,UITableViewDelegate,UITableViewDataSource, SwipeTableViewCellDelegate{
//TabelView
#IBOutlet weak var fTableView: UITableView!
//Variables
var fdata = [FData]()
override func viewDidLoad() {
super.viewDidLoad()
// FtableView
fTableView.delegate = self
fTableView.dataSource = self
//reload Data
fTableView.reloadData()
//Get Data
fdata = FavouriteData.instance.getData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fdata.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: FAVOURITE_CELL, for: indexPath) as? FavouriteCell{
cell.configureCell(fdata: fdata[indexPath.row])
//cell.delegate = self
return cell
}
return FavouriteCell()
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete")
return [deleteAction]
}
}

SearchBar with Custom Class

I am a beginner on iOS Development and I have an issue when I try to use the UISearchBar in a UITableView.
I do not know how to use the custom class in a table view which is filtering the data with a search bar. Please, would you fix the issue? Below is the custom class:
// Data.swift
// SearchBarWithTableView
class Food {
var FoodName:String = ""
var FoodQuantity:String = ""
var FoodGroup:String = ""
var FoodImage:String = ""
var FoodCarbs:Int = 0
var FoodCalories:Int = 0
init(foodName:String,foodGroup:String,foodQuantity:String,foodImage:String,foodCarbs:Int,foodCalories:Int) {
self.FoodName = foodName
self.FoodQuantity = foodQuantity
self.FoodGroup = foodGroup
self.FoodImage = foodImage
self.FoodCarbs = foodCarbs
self.FoodCalories = foodCalories
}}
ViewController.swift
import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate{
#IBOutlet weak var SearchBarTableView:UITableView!
#IBOutlet weak var SearchBar:UISearchBar!
var searchActive:Bool=false
var filteredData=[Food]()
let allData:[Food]=[Food(foodName: "Rice", foodGroup: "Beans", foodQuantity: "5 scope",foodImage:"1S.jpg", foodCarbs:15,foodCalories: 80),
Food(foodName: "Apple", foodGroup: "Frutie", foodQuantity: "One",foodImage:"S-2.jpg", foodCarbs: 15, foodCalories: 80),
Food(foodName: "ban 1",foodGroup: "Beans",foodQuantity:"One",foodImage:"3S.jpg",foodCarbs: 15,foodCalories: 80),
Food(foodName: "ban 2", foodGroup: "Beans", foodQuantity: "half pieace",foodImage:"4-S.jpg", foodCarbs: 25, foodCalories: 140)]
override func viewDidLoad() {
super.viewDidLoad()
SearchBarTableView.delegate=self
SearchBarTableView.dataSource=self
SearchBar.delegate=self
filteredData=allData
print(filteredData.count)
print("-----------")
print(allData.count)
SearchBar.returnKeyType=UIReturnKeyType.done
definesPresentationContext=true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "cell")
var Text:String
if searchActive {
Text=filteredData[indexPath.row].FoodName
}else{
Text=allData[indexPath.row].FoodName
}
cell?.textLabel?.text=filteredData[indexPath.row].FoodName
return cell!
}
func searchBar(_ searchBar: UISearchBar, textDidChange `searchText: String) {`
if SearchBar.text == nil || SearchBar.text == "" {
searchActive=false
view.endEditing(true)
SearchBarTableView.reloadData()
}else{
searchActive=true
/* here is the problem */
// filteredData=allData.filter({$0 === searchBar.text!})
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive {
return filteredData.count
}
return allData.count
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier=="Go"{
if let indexPath=SearchBarTableView.indexPathForSelectedRow{
let destinationVC=segue.destination as! DetailsViewController
destinationVC.namE=allData[indexPath.row].FoodName
destinationVC.quntitY=allData[indexPath.row].FoodQuantity
destinationVC.calorieS=allData[indexPath.row].FoodCalories
destinationVC.carbS=allData[indexPath.row].FoodCarbs
}
}
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange `searchText: String) {`
if SearchBar.text == nil || SearchBar.text == "" {
searchActive=false
view.endEditing(true)
SearchBarTableView.reloadData()
}else{
searchActive=true
filteredData=allData.filter({$0.FoodName === searchBar.text!})
SearchBarTableView.reloadData()
}
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text, !searchText.isEmpty {
self.filteredData = self.allData.filter{
var found = false
for str in ($0 as! NSDictionary) {
if(str.key as! String == "foodName")
{
var strin = str.value as! String;
strin = strin.lowercased();
let search = searchText.lowercased()
found = strin.contains(search);
}
}
return found
}
}
else {
self.Array = response as! [Any]
}
tableView.reloadData()
}
Use above method.

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

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

Resources