Displaying Search Results in UITableView - ios

I have a UITableViewController and I've added a search bar as the header view. I'm trying to get the table view data to reload when the text in the search bar changes but it's not working.
Here is my textDidChange method. For context, jsonArray is [[String: AnyObject]] and is passed from another view controller. The searchResults array is then set to jsonArray in viewDidLoad() and the table view uses searchResults as it's data source.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
searchResults?.removeAll()
searchResults = jsonArray!
} else {
searchResults?.removeAll()
for market in jsonArray! {
let titleRange = market["name"]?.range(of: searchText, options: .caseInsensitive)
var address = String()
var city = String()
var state = String()
var zip = Int()
if let address1 = market["address1"] as? String {
address = address1
}
if let city1 = market["city"] as? String {
city = city1
}
if let state1 = market["state"] as? String {
state = state1
}
if let zip1 = market["zip"] as? Int {
zip = zip1
}
let zipCode = String(describing: zip)
let addressString = String("\(address) " + "\(city), " + "\(state) " + "\(zipCode)")
let addressRange = addressString?.range(of: searchText, options: .caseInsensitive)
if titleRange != nil {
searchResults?.append(market)
} else if addressRange != nil {
searchResults?.append(market)
}
}
}
tableView.reloadData()
}
I've added this print statement at the end of the else statement after the for loop and it's showing that the array contains all of the objects it contained originally so it looks like my ranges aren't working correctly.
print("search array: \(searchResults?.count)")

I fixed this by using NSRange instead of Range in Swift.
let titleNSRange: NSRange = (market["name"]?.range(of: searchText, options: .caseInsensitive))!
let addressNSRange: NSRange = (addressNSString.range(of: searchText, options: .caseInsensitive))
if titleNSRange.location != NSNotFound || addressNSRange.location != NSNotFound {
searchResults?.append(market)
}

you Don't need to call searchResults?.removeAll() function instead you use it
searchResults = NSMutableArray.init()
Don't use searchResults?.append(market) instead use it
searchResults.addObject(market)

Related

Swift - Append to NSMutableDictionary

I am trying to append to an NSMutableDictionary with the following code:
let RSVPDirectory = NSMutableDictionary()
for i in 0..<self.RSVPs.count {
var tmp = self.RSVPs[i]
var firstLetter = String()
if(tmp["lastname"] is NSNull)
{
firstLetter = ((tmp["email"] as? NSString)?.substring(to: 1).uppercased())!
}
else
{
firstLetter = ((tmp["lastname"] as? NSString)?.substring(to: 1).uppercased())!
}
if RSVPDirectory[firstLetter] == nil {
RSVPDirectory[firstLetter] = [AnyHashable]()
}
RSVPDirectory[firstLetter] = tmp
}
My problem with this is that I am expecting multiple tmp inside RSVPDirectory[firstLetter] but it only adds the first one as if its overriding the previous tmp
How do I append to NSMutableDictionary in swift, I know in objective-c you can do this:
[[RSVPDirectory objectForKey:firstLetter] addObject:tmp];
What would be the equivalent to that in swift?
Try the below code in a playground you will see the output, hope this gives you an idea.
func upperCaseFirstLetter(_ str: String) -> String {
guard let first = str.first else { return "" }
return "\(first)".uppercased()
}
var RSVPs = [[String:String]]()
var RSVPDirectory = [String: [[String:String]]]()
//Test Data
var str = ["email":"test1#c.com"]
RSVPs.append(str)
str = ["lastname":"Atest2"]
RSVPs.append(str)
for i in 0..<RSVPs.count {
var tmp = RSVPs[i]
var firstLetter = ""
if(tmp["lastname"] == nil) {
firstLetter = upperCaseFirstLetter(tmp["email"]!)
} else {
firstLetter = upperCaseFirstLetter(tmp["lastname"]!)
}
if RSVPDirectory[firstLetter] == nil {
RSVPDirectory[firstLetter] = [[String:String]]()
}
RSVPDirectory[firstLetter]?.append(tmp)
}
print(RSVPDirectory)
This is the native Swift version of your Objective-C-ish code.
It uses the Dictionary(grouping API of Swift 4
let RSVPDirectory = Dictionary(grouping: RSVPs) { (dictionary) -> String in
if let lastName = dictionary["lastname"] as? String {
return String(lastName.prefix(1).uppercased())
} else if let email = dictionary["email"] as? String {
return String(email.prefix(1).uppercased())
} else {
return "🚫"
}
}
Yes you are actually replacing the RSVPDirectory[firstLetter], overriding it every time with new tmp.
What you are looking for is this:
//RSVPDirectory[firstLetter] = tmp //Replace this line with below code
let tempArray = RSVPDirectory[firstLetter] as? [AnyHashable]
tempArray?.append(tmp)
RSVPDirectory[firstLetter] = tmpArray
Here I have used a tempArray because we want to mutate the array. Accessing it directly and trying to append new value will in-turn try to mutate an immutable value. So first I have got the array in the tempArray and then after mutating the array I swapped it back in the dictionary with updated values.

How to convert NSManagedObject Array into String Array - Swift 3

I want to convert NSManagedObject Array into String Array for Search purposes. (I don't want to use predicates)
The search would be performed on an attribute "Notes" of an entity "Documents"
var documents: [NSManagedObject] = [] // data is saved in this after fetch request !
var filtered = [String]()
var dataa = [String]()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//the line below is not working
// dataa = [String(describing:documents)]
filtered = dataa.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
// let range = tmp.rangeOfString(searchText, options: NSString.CompareOptions.CaseInsensitiveSearch)
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = true;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
fetched data is stored into documents (NSManagedObject) through this function & it is called on viewDidLoad()
func fetchDocuments() {
let context = getContext()
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Documents")
//PLEASE TELL ME SOME CODE TO SAVE ALL FETCHED NOTES IN "dataa" SO THAT I CAN PERFORM FILTERING.
// It would be done like obtaining value from the nsmanagedobject via (abc.value(forKeyPath: "note") as? String)!
do {
documents = try context.fetch(fetchRequest)
self.tableView.reloadData()
} catch let error as NSError {
let errorDialog = UIAlertController(title: "Error!", message: "Failed to save! \(error): \(error.userInfo)", preferredStyle: .alert)
errorDialog.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(errorDialog, animated: true)
}
}

Filter searchText to tableView

Right now I am using following
let data = ["New York, NY", "Los Angeles, CA" . . .]
var filteredData: [String]!
filteredData = data
But I want to use Firebase, with an almost identical structure, by using this
var data = [Categories]()
(This is categories)
struct Categories {
let key:String!
let content:String!
let itemRef:FIRDatabaseReference?
init (content:String, key:String = "") {
self.key = key
self.content = content
self.itemRef = nil
}
init (snapshot:FIRDataSnapshot) {
key = snapshot.key
itemRef = snapshot.ref
if let CategoriesContent = snapshot.value!["content"] as? String {
content = CategoriesContent
} else {
content = ""
}
}
}
So that when I search for something these lines is supposed to filter out everything that aren't correct
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
// Unhide tableview, this will be changed to another method
tableView.hidden = false
filteredData = searchText.isEmpty ? data : data.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.rangeOfString(searchText) != nil
})
tableView.reloadData()
}
But since filter({(dataString: String) only takes strings it does not work
Question : Is there any other way to replace the string with my Firebase struct?
Thanks a lot!
this tutorial is so clear in UISearchResultsUpdating and Filtering section.

Cannot assign a value of type AnyObject to a value of type String

I have a function, which returns me an Array of AnyObject:
public func xmppRosterDidEndPopulating(sender: XMPPRoster?) {
let jidList = OneChat.sharedInstance.xmppRosterStorage.jidsForXMPPStream(OneChat.sharedInstance.xmppStream)
contacts = jidList
}
and I have an array: var contacts = [AnyObject]()
Later, I want to run my search function among these values:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filtered = contacts.filter({ (text) -> Bool in
let tmp: NSString = text as! NSString
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
but it returns me error:
Cannot assign a value of type AnyObject to a value of type String
in
filtered = contacts.filter({ (text) -> Bool in
how can I solve this problem?
The filter method returns an array of the same type as the input. In this case since contacts is [AnyObject] it will be returning [AnyObject].
If you are only dealing with strings the best approach here would be to change the declaration of contacts to represent strings and convert jidList to provide an array of strings...
if let jidList = OneChat.sharedInstance.xmppRosterStorage.jidsForXMPPStream(OneChat.sharedInstance.xmppStream) as? [String] {
contacts = jidList
} else {
// handle failure to convert to string array
}
The filtering code should then work as-is.

Swift Array of objects filtering

I've the following array of objects which I'm trying to filter and return for a searchDosplayController.
var family = [Family]()// fetchedFamily
var filteredFamily: [Family]! // filter fetched events
And that is how, I'm filtering it:
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = searchController.searchBar.text
self.filteredProvince = provinces
if !searchText.isEmpty {
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %#", searchText)
let array = (filteredProvince as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredProvince = array as! [Province]
}
However nothing is getting returned when I'm searching. And I tried to do it in this way:
filteredFamily = searchText.isEmpty ? family : family.filter({(dataString: String) -> Bool in
return dataString.rangeOfString(searchText, options: .CaseInsensitiveSearch) != nil
})
But, I'm receiving the following error: 'Family is not a subtype of String'. Is there any better way to filter the Family? Because, the filtered result has to be sent back to searchDisplayController.
Thanks in advance.
So we have a Family class that does look like this right?
class Family {
let name : String
init(name:String) {
self.name = name
}
}
Then we have a list of families:
var families = [Family]()
And we want to extract all the families where the name property contains a given text.
let searchText = "something here"
Good, first of all we add this extension to the String struct.
extension String {
func contains(find: String) -> Bool {
return self.rangeOfString(find) != nil
}
}
And finally we can filter the families writing:
let filtered = families.filter { $0.name.contains(searchText) }
Hope this helps.

Resources