searchbar textDidChange error - swift

I am trying to implement a searchbar for a tableView and I am receiving the error "...Binary operator '==' cannot be applied to operands of type 'Place' and 'String'" in my textDidChange method. The tableView is populated from a Firebase database "placeList" array. Not sure where the error source is coming from. Thanks in advance for any help!
lazy var searchBar:UISearchBar = UISearchBar()
var placeList = [Place]()
var placesDictionary = [String: Place]()
var isSearching = false
var filteredData = [Place]()
override func viewDidLoad() {
super.viewDidLoad()
searchBar.searchBarStyle = UISearchBarStyle.prominent
searchBar.placeholder = " Search Places..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
navigationItem.titleView = searchBar
tableView.allowsMultipleSelectionDuringEditing = true
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
if isSearching {
cell.textLabel?.text = filteredData[indexPath.row].place
} else {
cell.textLabel?.text = placeList[indexPath.row].place
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
// error in below line of code...
filteredData = placeList.filter({$0.place == searchBar.text})
tableView.reloadData()
}
}

Your property placeList is an array of Place objects. When you call the filter function on your array (placeList.filter({$0 == searchBar.text!})), what you are saying is "filter placeList where a Place object is equal to searchBar.text". A place object is not a String, you cannot compare two different types. I'm not familiar with your data model, or your Place class, but maybe you have some type of String property in your Place class which you could use to compare? For instance, say Place had a property called id of type String, you could then filter through comparison like so: filteredData = placeList.filter({$0.id == searchBar.text!}) - notice the added $0.id.
You can only compare a String to a String

Related

how to add a searchbar in a tableview with a parsed JSON file

I have successfully parsed a JSON file with the following data model into my project and my tableview.
import Foundation
struct ActionResult: Codable {
let data: [Datum]
}
struct Datum: Codable {
let goalTitle, goalDescription, goalImage: String
let action: [Action]
}
struct Action: Codable {
let actionID: Int
let actionTit: String
}
Now I am trying to create a searchbar to search on the "actionTitle". My tableview has section headers and rows.
Relevant code:
var filteredData: [Action]?
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
filteredData = ????
navigationItem.searchController = searchController
parseJSON()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == ""{
filteredData = ????
}
else {
for actions in ???? {
if actions.lowercased().contains(searchText.lowercased()) {
filteredData.append(actions)
}
self.tableView.reloadData()
}
I do not know what code to use where I have ????.
Thanks.
You want to keep an instance of all of the available data (allActions), and always show your filter data (filteredData) in the tableView. So when there is nothing to filter, filteredData is equal to allActions (unless you intend to hide all data when the search is empty).
When searchBar(_:,textDidChange:) is called, you can use filter(_:) to evaluate if the item should be included. Apple's description on the filter closure:
A closure that takes an element of the sequence as its argument and
returns a Boolean value indicating whether the element should be
included in the returned array.
I don't know if there is a specific reason for declaring filteredData: [Action]?, is it because the data is not populated until parseJSON() is called? If so--I suggest initializing an empty arrays and populating them when the data is available.
Also, does parseData() produce an instance of Datum? I believe this piece of your code is not included, so I am adding datum: Datum?.
If I am wrong, please provide more info what parseJSON() populates and I will update my answer.
var result: ActionResult? {
didSet {
guard let result = result else { return }
allSectionDataActionMap = Dictionary(uniqueKeysWithValues: result.data.enumerated().map { ($0.0, ($0.1, $0.1.actions)) })
updateFilteredData()
}
}
var allSectionDataActionMap = [Int: (datum: Datum, actions: [Action])]()
// Maps the section index to the Datum & filtered [Action]
var filteredSectionDataActions = [Int: (datum: Datum, actions: [Action])]()
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
// ...
parseJSON()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredSectionDataActions.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSectionDataActions[section]?.actions.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
if let action = filteredSectionDataActions[indexPath.section]?.actions[indexPath.row] {
// setup cell for action
cell.textLabel?.text = action.actionTitle
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateFilteredData(for: searchText.lowercased())
tableView.reloadData()
}
func updateFilteredData(for searchText: String = String()) {
if searchText.isEmpty {
filteredSectionDataActions = allSectionDataActionMap
} else {
for (index, (datum, actions)) in allSectionDataActionMap {
let filteredActions = actions.filter { $0.actionTitle.lowercased().contains(searchText) }
if filteredActions.isEmpty {
filteredSectionDataActions[index] = (datum, actions)
} else {
filteredSectionDataActions[index] = (datum, filteredActions)
}
}
}
}

SearchBar doesn't returns results until activated and typing

I see empty rows when my tableView is loaded for the first time. But if I activate searchBar, write something into textField everything works correctly even i clean all the text. I want the app works without these extra steps. But I don't understand where exactly I am making the mistake.
class NextTableViewCell: UITableViewController, UISearchBarDelegate, UISearchControllerDelegate {
#IBOutlet weak var searchBar: UISearchBar!
var ref: DatabaseReference?
let db = Firestore.firestore()
var messages: [Message] = []
var filteredMessages: [Message] = []
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.dataSource = self
loadMessages()
filteredMessages = messages
}
Func loadMessages retrieves data from Firebase
func loadMessages() {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print(e)
} else {
if let snapshotDocuments = querySnapshot?.data(){
for item in snapshotDocuments {
if let key = item.key as? String, let translate = item.value as? String {
let newMessage = Message(key: key, value: translate)
self.messages.append(newMessage)
}
}
DispatchQueue.main.async { [self] in
self.messages.sort(by: {$1.key > $0.key})
self.tableView.reloadData()
}
}
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredMessages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = filteredMessages[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ListVC", for: indexPath)
cell.textLabel?.text = message.key + " - " + message.value
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredMessages = []
if searchText == "" {
filteredMessages = messages
}else{
for item in messages {
if item.key.lowercased().contains(searchText.lowercased()){
if let key = item.key as? String, let translate = item.value as? String {
let newMessage = Message(key: key, value: translate)
self.filteredMessages.append(newMessage)
}
}
}
}
tableView.reloadData()
}
The problem is that filteredMessages is empty when the view controller loads and only gets populated when you search.
Since filteredMessages is essentially a subset of messages, you need to set filteredMessages to messages upon fetching them from the database.
Try adding a line that does that in your loadMessages() method:
DispatchQueue.main.async { [self] in
self.messages.sort(by: {$1.key > $0.key})
self.filteredMessages = self.messages // Add this line
self.tableView.reloadData()
}

search in retrieved data from json in swift3

I retrieve data from service URL in swift3 and displayed in tableview. I tried to search names, it shows filtered names but another cell is not updating. please check my below code.
class MyViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var filteredArray = [String]()
var shouldShowSearchResults = false
var nameArray = [String]()
var emailArray = [String]()
var tableData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
downloadData()
createSearchBar()
// Do any additional setup after loading the view.
}
func createSearchBar(){
searchBar.showsCancelButton = false
searchBar.placeholder = "Enter your search"
searchBar.delegate = self
self.navigationItem.titleView = searchBar
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tableData = nameArray
if shouldShowSearchResults
{
return filteredArray.count
}
else
{
return tableData.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:mycell! = tableView.dequeueReusableCell(withIdentifier: "cell") as! mycell
if shouldShowSearchResults
{
cell.name.text = filteredArray[indexPath.row]
cell.email.text = emailArray[indexPath.row]
return cell
}
else
{
cell.name.text = tableData[indexPath.row]
cell.email.text = emailArray[indexPath.row]
return cell
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
searchBar.endEditing(true)
self.tableView.reloadData()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredArray = tableData.filter({
(names:String) -> Bool in
return names.lowercased().range(of: searchText.lowercased()) != nil
})
if searchText != ""
{
shouldShowSearchResults = true
self.tableView.reloadData()
}
else
{
shouldShowSearchResults = false
self.tableView.reloadData()
}
}
func downloadData()
{
let url = URL(string: "http://www.json-generator.com/api/json/get/crdvbKvLoy?indent=2")!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 10000)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
if let jsonData = try JSONSerialization.jsonObject(with:data!, options: []) as? [[String:AnyObject]] {
print(jsonData)
// Utility.SharedInstance.dict_UserDetails3 = jsonData as AnyObject
for item in jsonData {
if let name = item["Name"] as? AnyObject {
self.nameArray.append(name as! String)
}
if let email = item["Email"] as? AnyObject{
self.emailArray.append(email as! String)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch let error as NSError {
print(error)
}
}.resume()
}
}
output:
search___________
Name Email
----------------
Wasim wasim#gmail.com
Dravid dravid#gmail.com
Kohli virat#gmail.com
Kallis Jaques#gmail.com
I entered in search text as K
-----------------------------
search_____K______
Name Email
----------------
Kohli wasim#gmail.com
Kallis dravid#gmail.com
In this search works, but Email field not updating. please check once. I am tried more samples but not solved. please check once, what changes I do to solve this problem.
you filter name array but not the email address array. so the indexes don't match up.
The best solution is to not separate related data. Structure it properly.
struct Person {
var name: String
var email: String
}
var people: [Person]?
var filteredResults: [Person]?
Using a struct for a person that holds the name and email together, you can filter this array easier and the data wont get out of sync.

Expandable cells in Swift table - cell reuse/dequeue?

I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}
OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.

How to implement UISearchController in iOS8?

I have tried to implement the UISearchController in IOS8 but failed.
The problem is when I have changed the text and the scope button, noting is presented to me.
And it seems that the updateSearchResultsForSearchController function is not even called when I update the search Bar or the scope button.
Here is my code:
class SearchTestController: UITableViewController, UISearchResultsUpdating {
struct Candy {
let category : String
let name : String
}
var searchcontroller = UISearchController(searchResultsController: nil)
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredcandy = candies.filter() { (candy:Candy) -> Bool in
let scopetest = ( self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] == "All" ) || ( candy.category == self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] )
//let texttest = candy.name.rangeOfString(self.searchcontroller.searchBar.text)
//let result = scopetest && (texttest != nil)
return scopetest
}
println(filteredcandy.count)
self.tableView.reloadData()
}
var candies = [Candy]()
var filteredcandy = [Candy]()
var category = ["Chocolate","Hard","Other","All"]
override func viewDidLoad() {
super.viewDidLoad()
// Sample Data for candyArray
self.candies = [Candy(category:"Chocolate", name:"chocolate Bar"),
Candy(category:"Chocolate", name:"chocolate Chip"),
Candy(category:"Chocolate", name:"dark chocolate"),
Candy(category:"Hard", name:"lollipop"),
Candy(category:"Hard", name:"candy cane"),
Candy(category:"Hard", name:"jaw breaker"),
Candy(category:"Other", name:"caramel"),
Candy(category:"Other", name:"sour chew"),
Candy(category:"Other", name:"gummi bear")]
// Reload the table
self.tableView.reloadData()
self.tableView.tableHeaderView = searchcontroller.searchBar
searchcontroller.searchBar.sizeToFit()
searchcontroller.searchBar.showsSearchResultsButton = true
self.definesPresentationContext = true
searchcontroller.searchBar.scopeButtonTitles = category
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchcontroller.active {
return self.candies.count
} else {
return self.candies.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
var candy : Candy
// Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
if searchcontroller.active {
candy = filteredcandy[indexPath.row]
} else {
candy = candies[indexPath.row]
}
// Configure the cell
cell.textLabel!.text = candy.name
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
Add the following lines to viewDidLoad()
searchcontroller.searchResultsUpdater = self
searchcontroller.delegate = self
Update:
Add the following line in viewDidLoad()
searchcontroller.searchBar.delegate = self
Then update the search results in searchBar(_:selectedScopeButtonIndexDidChange:)