table view does not reload after delete letter in search bar - ios

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

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

Swift SearchBar inTableView doesn't show the correct Data in the filtered rows

My app is using Firebase Realtime Database to store information of each user. The tableView works fine. I added a searchBar and when I type letters in the searchBar, I can see that the amount of rows presented in the tableView is equal to the amount of users which contain these letters in their names. The problem is that the rows presented in the tableview (after typing letters in the search bar), contain the information of the first user till x user (x = amount of users which contain these letters in their names).
import UIKit
import FirebaseDatabase
import Firebase
class VideoListScreen: UIViewController {
#IBOutlet weak var tableView: UITableView!
var blogPost: [BlogPost] = []
var searchBlogPost = [BlogPost]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
blogPost = []
createArray()
tableView.delegate = self
tableView.dataSource = self
}
func createArray() {
let ref = Database.database().reference().child("Users")
ref.observe(.childAdded, with: { (snapshot) in
if let postDict = snapshot.value as? [String : String] {
let post = BlogPost(name:postDict["name"] ?? "" , gaf: postDict["gaf"] ?? "", place: postDict["place"] ?? "", phone: postDict["phone"] ?? "", notes: postDict["notes"] ?? "", elsertext: postDict["elsertext"] ?? "")
self.blogPost.append(post)
self.tableView.reloadData()
}
})
}
}
extension VideoListScreen: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching{
return searchBlogPost.count
}else{
return blogPost.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let blogp = blogPost[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Tavla") as! Tavla
if searching{
cell.setBLogPost(blogPost: searchBlogPost[indexPath.row])
}
else{
cell.setBLogPost(blogPost: blogPost[indexPath.row])
}
cell.setBLogPost(blogPost: blogp)
return cell
}
}
extension VideoListScreen: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBlogPost = blogPost.filter({$0.name.prefix(searchText.count) == searchText})
searching = true
tableView.reloadData()
}
}
I Believe the problem is in the if and else statments in this function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let blogp = blogPost[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Tavla") as! Tavla
if searching{
cell.setBLogPost(blogPost: searchBlogPost[indexPath.row])
}
else{
cell.setBLogPost(blogPost: blogPost[indexPath.row])
}
cell.setBLogPost(blogPost: blogp)
return cell
}
You have extra cell.setBLogPost(blogPost: blogp) in your delegate methode
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let blogp = blogPost[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Tavla") as! Tavla
if searching{
cell.setBLogPost(blogPost: searchBlogPost[indexPath.row])
}
else{
cell.setBLogPost(blogPost: blogPost[indexPath.row])
}
return cell
}
I think this will solve your problem and you can use
import UIKit
import FirebaseDatabase
import Firebase
class VideoListScreen: UIViewController {
#IBOutlet weak var tableView: UITableView!
var blogPost: [BlogPost] = []
var searchBlogPost = [BlogPost]()
override func viewDidLoad() {
super.viewDidLoad()
blogPost = []
createArray()
tableView.delegate = self
tableView.dataSource = self
}
func createArray() {
let ref = Database.database().reference().child("Users")
ref.observe(.childAdded, with: { (snapshot) in
if let postDict = snapshot.value as? [String : String] {
let post = BlogPost(name:postDict["name"] ?? "" , gaf: postDict["gaf"] ?? "", place: postDict["place"] ?? "", phone: postDict["phone"] ?? "", notes: postDict["notes"] ?? "", elsertext: postDict["elsertext"] ?? "")
self.blogPost.append(post)
self.searchBlogPost = self.blogPost
self.tableView.reloadData()
}
})
}
}
extension VideoListScreen: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchBlogPost.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let blogp = searchBlogPost[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Tavla") as! Tavla
cell.setBLogPost(blogPost: blogp)
return cell
}
}
extension VideoListScreen: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if ((searchText.trimmingCharacters(in: .whitespaces).isEmpty) {
searchBlogPost = blogPost
} else {
searchBlogPost = blogPost.filter({$0.name.prefix(searchText.count) == searchText})
}
tableView.reloadData()
}
}
I hope this will helps.

index out of range UISearchController

I am trying to obtain the behaviour you find in whatsapp when trying to Add Participants to a group. In a tableView I have implemented UISearchController.
When a new row is selected in tableView, it is assigned an filled image(see screenshot below) and it is added/deleted to/from selectedUsers. When I start searching in the searchBar cells get rearranged and I get index out of range error, which is normal. I don't know where to go from here.
Scope:
allow the user to add/remove users whether searchBar is active or not
make sure there are no duplicate users in selectedUsers. This will be saved to DataBase.
remove cancel button that is attached on searchBar.
What have I tried?
class GroupRegistrationViewController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [User]() //users that currentUser is following
var currentUser: User!
var selectedUsers = [User]() //selected users in this controller, selected users we follow
var filteredUsers = [User]() //this property will hold the Users that the currentUser is searching for
override func viewDidLoad() {
super.viewDidLoad()
self.fetchUsers()
navigationItem.title = "GroupRegistration"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.placeholder = "Search Members"
searchController.hidesNavigationBarDuringPresentation = false
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//simply check whether the user is searching or not
if isFiltering() {
return filteredUsers.count
}
return usersArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GroupRegistrationTableViewCell") as! GroupRegistrationTableViewCell
if isFiltering() {
cell.user = filteredUsers[indexPath.row]
}else{
cell.user = usersArray[indexPath.row]
}
if selectedUsers.contains(cell.user) {
//User type conforms to Equatable protocol so check is allowed
cell.added = true
}
return cell
}
//this method determines if you are currently filtering results or not
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func searchBarIsEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! GroupRegistrationTableViewCell
cell.added = !cell.added
if cell.added == true {
self.addRecipient(user: cell.user)
}else{
let index = selectedUsers.index(of: cell.user)!
self.deleteRecipient(user: cell.user, index: index)
}
}
// - Helper methods
// - add
// - delete
func addRecipient(user: User) {
self.selectedUsers.append(user)
}
func deleteRecipient(user: User, index: Int) {
selectedUsers.remove(at: index)
}
}//end of class
extension GroupRegistrationViewController {
//get users from firebase
func fetchUsers() {
DatabaseReference.users(uid: currentUser.userUID).reference().child("follows").observe(.childAdded, with: { (snapshot) in
let user = User(dictionary: snapshot.value as! [String:Any])
self.usersArray.insert(user, at: 0)
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.insertRows(at: [indexPath], with: .fade)
})
}
}
//UISearchResultsUpdating Delegate
extension GroupRegistrationViewController: UISearchResultsUpdating {
func filteredContentForSearchText(searchText: String, scope: String = "All") {
filteredUsers = usersArray.filter({ (user) -> Bool in
return user.fullName.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
/*
whenever the user adds or removes text in the search bar, the UISearchController will inform the GroupRegistrationViewController class of the change via a call to updateSearchResults(for:), which in turn calls filterContentForSearchText(_:scope:).
*/
func updateSearchResults(for searchController: UISearchController) {
filteredContentForSearchText(searchText: searchController.searchBar.text!)
searchController.searchBar.showsCancelButton = false
//CancelButton shows up when I tap for the first time in the search bar. !!!! - not desired behaviour.
//CancelButton disapears when I start typing
}
}
`class GroupRegistrationTableViewCell: UITableViewCell {`
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var displayNameLabel: UILabel!
#IBOutlet weak var checkboxImageView: UIImageView!
#IBOutlet weak var fullNameTextLabel: UILabel!
var user: User! {
didSet {
self.updateUI()
}
}
//when a row is selected added property is assigned true/false value
var added: Bool = false {
didSet{
if added == false {
checkboxImageView.image = UIImage(named: "icon-checkbox")
}else {
checkboxImageView.image = UIImage(named: "icon-checkbox-filled")
}
}
}
//add cache here
var cache = SAMCache.shared()
func updateUI() {
let profilePictureKey = "\(user.userUID)-profilePicture"
if let image = cache?.image(forKey: profilePictureKey){
self.profileImageView.image = image
self.profileImageView.layer.cornerRadius = self.profileImageView.bounds.width / 2.0
self.profileImageView.layer.masksToBounds = true
} else {
user.downloadProfilePicture { (image, error) in
self.cache?.setImage(image, forKey: profilePictureKey)
self.profileImageView.image = image
self.profileImageView.layer.cornerRadius = self.profileImageView.bounds.width / 2.0
self.profileImageView.layer.masksToBounds = true
}
}
displayNameLabel.text = user.username
checkboxImageView.image = UIImage(named: "icon-checkbox")
fullNameTextLabel.text = user.fullName
}
`}//end of class`

Swift 3/4: SearchBar not filtering results properly in TableView

I have a popup with searchBar at the top and TableView below it. TableView is populated by dynamic data. I have a custom tableViewCell, with a label for names and a checkBox(M13CheckBox Library) to select a name.
Now, when I search for a name, Firstly the tableView is not loaded as the user types a name in the search bar. For eg, Suppose there are persons named "Mary", "Mackenzie", "Margaret" and "Mallory". I want to search for "Margaret", so as I start typing "Mar" in searchBar, then "Mary" and "Margaret" are filtered properly in tableView, but when I go back i.e "Ma", then it should show all the 4 names, since "Ma" is present in the list, But the tableView does not show anything.
So tableView should always reload as user types in searchBar if the letters are contained in the names. Please help me sort this issue. Since it is a popup I am passing data to tableView from another VC, by notification.
Here is my code for search VC:
class ParticipantsListVC: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
public static var participantNameArray:[String] = [String]() //global var
var viewController: ViewController!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchParticipantFilter.delegate = self
viewController = ViewController()
let notificationName = NSNotification.Name("reloadList")
NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: OperationQueue.main) { (notifObject) in
self.tableView.reloadData()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText:String) {
if searchText == "" {
ParticipantsListVC.participantNameArray.removeAll()
viewController.getParticipantList() // func to get list from sever
}else{
ParticipantsListVC.participantNameArray = ParticipantsListVC.participantNameArray.filter({(name) -> Bool in
return name.lowercased().contains(searchText.lowercased())
})
}
self.tableView.reloadData()
}
}
Also if I select a name, then checkBox is selected in front of that name.But when I click on cancel(X) in searchBar, then always the first cell in tableView is shown selected and not the name that I had selected. I don't know why always the first cell gets selected, after selecting name from filtered list.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ParticipantListCell
let dict = ParticipantsListVC.participantNameArray[indexPath.row]
cell.participantNameLabel.text = dict
if selectedIndexPaths.contains(indexPath) {
cell.selectedParticipantCB.setCheckState(.checked, animated: true)
}else{
cell.selectedParticipantCB.setCheckState(.unchecked, animated: true)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Since any random cell was getting selected on scrolling So I added this code.
tableView.deselectRow(at: indexPath, animated: true)
if selectedIndexPaths.contains(indexPath) {
selectedIndexPaths.removeObject(object: indexPath)
}else{
selectedIndexPaths.append(indexPath)
}
tableView.reloadData()
}
I don't want to use searchBar in headerView or another tableView to show filtered list. Please much appreciated.Thank you.
You need to create another array to hold the backup of data array.
var arrParticipantList = [String]()
var arrParticipantListBackup = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchBar.delegate = self
self.tblParticipantList.delegate = self
self.tblParticipantList.dataSource = self
self.arrParticipantList = ["Mary", "Mackenzie", "Margaret", "Mallory","Molly"]
self.arrParticipantListBackup = self.arrParticipantList
}
Code to search for search string, refill array and reload tableview
extension ViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
var searchText = searchBar.text! + text
if range.length > 0 {
if range.location == 0 {
self.arrParticipantList = self.arrParticipantListBackup
self.tblParticipantList.reloadData()
return true
}
searchText = String(searchText.dropLast(range.length))
}
self.arrParticipantList = self.arrParticipantListBackup.filter({$0.lowercased().hasPrefix(searchText.lowercased())})
self.tblParticipantList.reloadData()
return true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.searchBar.text = ""
self.searchBar.resignFirstResponder()
self.arrParticipantList = self.arrParticipantListBackup
self.tblParticipantList.reloadData()
}
}
Code for tableview
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arrParticipantList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = self.arrParticipantList[indexPath.row]
return cell!
}
}
Hope this solves your issue.
struct namelist {
var searchname: NSString
}
var searchActive = Bool()
var newSearchArray = [namelist]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchActive ? newSearchArray.count : nameOldArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Cell:SearchTableViewCell! = tableView.dequeueReusableCell(withIdentifier: "Cell") as! SearchTableViewCell
Cell.selectionStyle = .none
if (searchActive == true) {
if ( newSearchArray.count > 0) {
var para = NSMutableAttributedString()
para = NSMutableAttributedString(string:(newSearchArray[indexPath.row].searchname) as String)
do {
let regex = try NSRegularExpression(pattern: searchText, options: NSRegularExpression.Options.caseInsensitive )
let nsstr = newSearchArray[indexPath.row].searchname
text = nsstr as String
let all = NSRange(location: 0, length: nsstr.length)
var matches : [String] = [String]()
regex.enumerateMatches(in: text, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: all) {
(result : NSTextCheckingResult?, _, _) in
if let r = result {
let results = nsstr.substring(with: r.range) as String
matches.append(results)
let substringrange = result!.rangeAt(0)
para.addAttribute(NSForegroundColorAttributeName, value:UIColor.init(red: 237/255.0, green: 60/255.0, blue: 58/255.0, alpha: 1.0), range: substringrange)
Cell.namelbl.attributedText = para
}
}
} catch {
}
}
}
else {
Cell.namelbl.text = self.searchname[indexPath.row] as? String
}
return Cell
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchArray.removeAllObjects()
newSearchArray.removeAll()
if Search.text != nil {
for i in 0 ..< searchname.count {
searchText = Search.text!
text = ((searchname.object(at: i))) as! String
if text.lowercased().contains(searchText.lowercased()) {
let elm = namelist(searchname: text as NSString)
self.newSearchArray.append(elm)
}
}
}
searchActive = !newSearchArray.isEmpty
searchBar.resignFirstResponder()
yourTableName.reloadData()
}

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