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.
Related
I have a class such as this
class FoundItem : NSObject {
var id : String!
var itemName : String!
var itemId : Int!
var foundBy : String!
var timeFound : String!
init(id: String,
itemName: String,
itemId: Int,
foundBy: String,
timeFound: String)
{
self.id = id
self.itemName = itemName
self.itemId = itemId
self.foundBy = foundBy
self.timeFound = timeFound
}
and I reference it on my
class MapViewVC: UIViewController, MKMapViewDelegate {
var found = [FoundItem]()
var filterItemName : String()
}
My FoundItem are generated by into an array of dictionaries from my class of FoundItem from a firebase query. I then get a string of that itemName that is generated from an another view controller that is a collection view on the didSelection function. I want to take that string and then filter or search the arrays with the string itemName that is equal from the itemName string from my previous viewController. Then removed the array of dictionaries that are not equal to the itemName. Not just the objects, but the entire array that contains non-equal key, value pair. I have looked for days, and I am stuck on filtering an array of dictionaries created from a class. I have looked and tried NSPredicates, for-in loops, but all that ends up happening is creating a new array or bool that finds my values or keys are equal. Here is the current function I have written.
func filterArrayBySearch() {
if self.filterItemName != nil {
dump(found)
let namePredicate = NSPredicate(format: "itemName like %#", "\(filterItemName)")
let nameFilter = found.filter { namePredicate.evaluate(with: $0) }
var crossRefNames = [String: [FoundItem]]()
for nameItemArr in found {
let listName = nameItem.itemName
let key = listName
if crossRefNames.index(forKey: key!) != nil {
crossRefNames[key!]?.append(nameItemArr)
if !("\(key)" == "\(filterItemName!)") {
print("------------- Success have found [[[[[[ \(key!) ]]]]]] and \(filterItemName!) to be equal!!")
// crossRefNames[key!]?.append(nameItemArr)
} else {
print("!! Could not find if \(key!) and \(filterItemName!) are equal !!")
}
} else {
crossRefNames[key!] = [nameItemArr]
}
}
} else {
print("No Data from Search/FilterVC Controller")
}
}
Can anyone help? It seems like it would be the simple task to find the value and then filter out the dictionaries that are not equal to the itemName string, but I keep hitting a wall. And running into for-in loops myself :P trying different things to achieve the same task.
I hope I understood what you were asking. You mention an "array of dictionaries" but you don't actually have an array of dictionaries anywhere in the code you've posted.
As far as I can tell, you are asking how to find all the entries in the found array for which itemName equals the filterItemName property.
If so, all you should need to do is:
let foundItems = found.filter { $0.itemName == filterItemName }
That's it.
Some other ideas:
If you want to search for items where filterItemName is contained in the itemName, you could do something like this:
let foundItems = found.filter { $0.itemName.contains(filterItemName) }
You could also make use of the lowercased() function if you want to do case-insensitive search.
You could also return properties of your found elements into an array:
let foundIds = found.filter { $0.itemName == filterItemName }.map { $0.itemId }
Sort array of dictionary using the following way
var dict:[[String:AnyObject]] = sortedArray.filter{($0["parentId"] as! String) == "compareId"}
The filter function loops over every item in a collection, and returns a collection containing only items that satisfy an include condition.
We can get single object from this array of dictionary , you can use the following code
var dict = sortedArray.filter{($0["parentId"] as! String) == "compareId"}.first
OR
let dict = sortedArray.filter{ ($0["parentId"] as! String) == "compareId" }.first
Local search filter using predicate in array of dictionary objects
with key name this code use for both swift3 and swift4,4.1 also.
func updateSearchResults(for searchController:
UISearchController) {
if (searchController.searchBar.text?.characters.count)! > 0 {
guard let searchText = searchController.searchBar.text,
searchText != "" else {
return
}
usersDataFromResponse.removeAll()
let searchPredicate = NSPredicate(format: "userName
CONTAINS[C] %#", searchText)
usersDataFromResponse = (filteredArray as
NSArray).filtered(using: searchPredicate)
print ("array = \(usersDataFromResponse)")
self.listTableView.reloadData()
}
}
Here I use CoreData And I have Array of Dictionary .
Here I am filter the key paymentToInvoice that value is invoice array then key invoiceToPeople that key contain People Dictionary then I search FirstName, lastName, Organization multiple key contain searchText.
I hope it's helps Please try this Thank You
var searchDict = dict.filter { (arg0) -> Bool in
let (key, value) = arg0
for paymentInfo in (value as! [PaymentInfo]){
let organization = (Array((value as! [PaymentInfo])[0].paymentToInvoice!)[0] as! InvoiceInfo).invoiceToPeople?.organization
let firstName = (Array((value as! [PaymentInfo])[0].paymentToInvoice!)[0] as! InvoiceInfo).invoiceToPeople?.firstName
let lastName = (Array((value as! [PaymentInfo])[0].paymentToInvoice!)[0] as! InvoiceInfo).invoiceToPeople?.lastName
return organization?.localizedStandardRange(of: searchText) != nil || firstName?.localizedStandardRange(of: searchText) != nil || lastName?.localizedStandardRange(of: searchText) != nil
}
return true
}
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)
I'm trying to add a search bar to the top of a grouped table. However I'm unsure how to filter through my data. The data is stored in a nested array with objects held in this format;
struct Group {
var id: String
var type: String
var desc: String
var avatar: String
var name: String
init() {
id = ""
type = ""
desc = ""
avatar = ""
name = ""
}
}
Because I get data from two sources, two arrays are nested together, this also makes it simpler to create the two sections of the grouped table. I'll note, they both use the same Group struct.
self.masterArray = [self.clientArray, self.departmentArray]
This "masterArray" is then used to populate the table. Filtering/searching a single array isn't too difficult, but how do I search through a nested array?
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
}
EDIT:
I've finally got things working, courtesy of #appzYourLife.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print("Searching for:" + searchText)
if searchText.isEmpty {
filterArray = masterArray
} else {
filterArray = [ clientArray.filter { $0.name.range(of: searchText) != nil }] + [ departmentArray.filter { $0.name.range(of: searchText) != nil } ]
}
tableView.reloadData()
}
You can make use of .flatten() to flatten your array prior to filtering it for whatever search criteria you want to use. E.g.
struct Group {
var id: String
var name: String
init(_ id: String, _ name: String) { self.id = id; self.name = name }
/* .... */
}
let clientArray = [Group("a", "John"), Group("b", "Jane"), Group("c", "Phil")]
let departmentArray = [Group("a", "Foo"), Group("b", "Bar"),
Group("c", "Baz"), Group("d", "Bax")]
let arr = [clientArray, departmentArray]
// find some id
let searchForId = "c"
let hits = arr.flatten()
.filter { $0.id.lowercaseString.containsString(searchText.lowercaseString) }
print(hits)
// [Group(id: "c", name: "Phil"), Group(id: "c", name: "Baz")]
From the edits of your questions, it seems, however, that you want the resulting filtered array to be of the same nested array type as the "master" array. In such case, the following is a more appropriate approach:
/* ... */
// find some id but keep [[Group]] type
let searchText = "c"
let hits = arr.map { $0.filter { $0.id.lowercaseString.containsString(searchText.lowercaseString) } }
print(hits)
// [[Group(id: "c", name: "Phil")], [Group(id: "cc", name: "Baz")]]
Given
struct Group {
let id: String = ""
let type: String = ""
let desc: String = ""
let avatar: String = ""
let name: String = ""
}
let clients = [Group(), Group(), Group(), Group()]
let departmens = [Group(), Group(), Group(), Group()]
let clientsAndDepartments = [clients, departmens]
You can search inside clients and department writing
let results = (clients + departmens).filter { $0.id == "123" }
Update #1
Now understand that you want to filter both arrays but as result you still want something like this [[Group]].
So here's the code
var filterArray = [clients.filter { $0.name == "White" }] + [departmens.filter { $0.name == "White" }]
Update #2
If you want to search for string inclusione the use this code
var filterArray = [ clients.filter { $0.name.rangeOfString("White") != nil }] + [ departmens.filter { $0.name.rangeOfString("White") != nil } ]
You can map over each of the arrays, and filter them independently:
self.masterArray.map{ subarray in
subarray.filter { element in
trueWhenElementShouldStay(element)
}
}
P.S. I suspect masterArray should NOT be an instance variable, it would be more appropriate as a local variable instead.
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.
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.