Implementing search result for searching inside dictionary? - swift

I have a dictionary like,
var DataDict:[String:[String]] = [String:[String]]()
DataDict[“fruits”] = [“apple”,”orange”,”pineapple”,”grape”]
DataDict[“vehicle”] = [“car”,”cycle / scooter“,”bike”]
DataDict[“colours”] = [“black”,”white”,”yellow”,”green”,”blue”]
so when I search in the search bar, if the searchText is fruits then the tableview should display the full array of fruits, or else whatever the searchText matches to the single object inside each key of DataDict?
So how will I achieve that to display the tableview. I need to implement that in searchBar textDidChange delegate.
Finllay I need to the display the result as, DataDict object as title and It's respective key as Subtitle.
ex:
apple
fruits
pineapple
fruits

You can do like this,
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle / scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
let filterarray = Array(DataDict.keys).filter { $0.contains("searchText")}
print("\(filterarray)")
for string in filterarray {
print("\(DataDict[string]!)")
}
Now you can display the as your requirement with filterarray

try below method for searchingtext. Result can be displayed to table
func searchText(string:String) -> [String] {
let text = string
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle / scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
var searchedItems = [String]()
for key in DataDict.keys {
if text == key {
if let items = DataDict[key] {
searchedItems.removeAll()
searchedItems.append(contentsOf: items)
}
break;
}
else {
if let items = DataDict[key] {
let filterd = items.filter({ (x) -> Bool in
return x.lowercased().contains(text.lowercased())
})
if filterd.count > 0 {
searchedItems.append(contentsOf: filterd)
}
}
}
}
print("SearchedItems: \(searchedItems)")
return searchedItems
}

You may get array in this way also for faster response..
let arrTemp = Array(DataDict.keys).filter { $0.contains(searchBar.text!)}
print(DataDict[arrTemp[0]])
Hope it will work for you.. :)

I know its kind of late , but wanted to post an improved answer with the use of flatMap and filter.
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle/scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
let searchText = "orange"
func search() {
var resultsArray = [String]()
if DataDict[searchText] != nil {
resultsArray = DataDict[searchText] ?? []
}else {
resultsArray = DataDict.flatMap{$0.1}.filter{ $0 == searchText}
}
print(resultsArray)
}
With the use of flat map you do not need to iterate each array of string as it flattens your nested dict into one. For more info on flatMap https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
Hope this helps.

Create Empty array and initialize it will first key of the DataDict and make it the dataSource of the tableView
then every search replace it's contents with the new one that matches the search and reload the tableView

Related

How to filter by grand parent value when deleting multiple child objects in realm?

I'm trying to delete all WSR objects that are associated with all Exercise objects within a particular workout object.
I just have some trouble filtering out all the WSR objects. The way it's set up right now, if I have multiple Exercise objects, only the WSR values associated with the first Exercise object get deleted, but not the rest.
How could I filter out all WSR objects that are associated with all Exercise objects within a particular workout?
class Days : Object {
#objc dynamic var weekday : String = ""
let workout = List<Workouts>()
}
class Workouts : Object {
#objc dynamic var title : String = ""
var parentDay = LinkingObjects(fromType: Days.self, property: "workout")
let exercise = List<Exercises>()
}
class Exercises : Object {
#objc dynamic var exerciseName : String = ""
var parentWorkout = LinkingObjects(fromType: Workouts.self, property: "exercise")
let wsr = List<WeightSetsReps>()
}
class WeightSetsReps : Object {
#objc dynamic var weight = 0
#objc dynamic var reps = 0
var parentExercise = LinkingObjects(fromType: Exercises.self, property: "wsr")
}
if days?[indexPath.section].workout[indexPath.row].exercise.isEmpty == false {
if let selectedWorkout = days?[indexPath.section].workout[indexPath.row] {
let thisWorkoutsExercises = realm.objects(Exercises.self).filter("ANY parentWorkout == %#", selectedWorkout)
// Filter function to get all wsr's associated with the selected workout...
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise == %#", days?[indexPath.section].workout[indexPath.row].exercise[indexPath.row])
realm.delete(thisWorkoutsWsr)
realm.delete(thisWorkoutsExercises)
realm.delete((days?[indexPath.section].workout[indexPath.row])!)
}
} else {
realm.delete((days?[indexPath.section].workout[indexPath.row])!)
}
The problem is this line:
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise == %#", days?[indexPath.section].workout[indexPath.row].exercise[indexPath.row])
By using ANY parentExercise == ... .exercise[indexPath.row]) you request only WSRs from exercise at one index. Imho this could even crash, if this workouts exercise count is lower than the days workout count.
By using the ANY .. IN .. operator, you request all exercises in the exercise array.
Try this:
guard let workout = days?[indexPath.section].workout[indexPath.row] else { return }
if workout.exercise.isEmpty {
realm.delete(workout)
return
}
let thisWorkoutsExercises = realm.objects(Exercises.self).filter("ANY parentWorkout == %#", workout)
// Filter function to get all wsr's associated with the selected workout...
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise IN %#", thisWorkoutsExercises)
realm.delete(thisWorkoutsWsr)
realm.delete(thisWorkoutsExercises)
realm.delete(workout)

How do I filter entire object in searchBar

In my Xcode project I have a tableView with a list of data. I have implemented a searchBar to filter user input. I would like it to filter my entire object var contactArray = [ExternalAppContactsBook]() but for some reason I'm only allowed to filter its properties, like contactArray.firstName. Here's what it looks like in searchbar function:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text?.count == 0 {
isFiltered = false
}else {
print("HELLO")
isFiltered = true
filteredName = contactArray.filter({$0.firstName.lowercased().prefix(searchText.count) == searchText})
}
tableViewOutlet.reloadData()
}
So, instead of $0.firstName I would like to have $0.prefix but the compiler says that there is no such property.
With $0.firstName I can only search first names on the list which is pretty limiting... I'm going about this the right way or what should I do?
Edit
ExternalAppContactsBook look like this:
class ExternalAppContactsBook {
var firstName = ""
var lastName = ""
var phoneNumber = ""
var company = ""
}
I have rewritten my code, but I run into the same problem. This is what I've got now in searchBar textDidChange:
filteredName = contactArray.filter({ object -> Bool in
guard let text = searchBar.text else {return false}
return object.firstName.contains(text)
})
This works, but it only works when I search on the first name of the people on the list. I want to be able to search their last names as well.
So I tried adding another one:
filteredLastName = contactArray.filter({ object -> Bool in
guard let text = searchBar.text else {return false}
return object.lastName.contains(text)
})
Then in cellForRowAt_
if isFiltered {
contactName = filteredName[indexPath.row].firstName
contactLastName = filteredLastName[indexPath.row].lastName
...}
But this obviously doesn't work and provide a problem with how many rows that should be returned in numberOfRowsInSection
You could simply use a condition with OR (||) when filtering
let filteredName = contactArray.filter {$0.firstName.starts(with: searchStr) || $0.lastName.starts(with: searchStr)}
You could also add this as a function to your class
func isMatch(_ searchString: String) -> Bool {
return firstName.starts(with: searchString) || lastName.starts(with: searchString)
}
And then filter
let filteredName = contactArray.filter {$0.isMatch(searchStr)}

How to search a string or character which is in [[String]] type

//Am not getting any search list of cells
//when am searching am getting empty array every time.
var actors = [["a0","b0","c0","d0"],["a1","b1","c1","d1"],["a2","b2","c2","d2"],["a3","b3","c3","d3"]]
var filterArr:[[String]] = []
func updateSearchResults(for searchController: UISearchController) {
print("Searching for text:----\(searchController.searchBar.text!)")
let predicate = NSPredicate(format: "SELF Contains[c] %#" , searchController.searchBar.text!)
filterArr = (actors as NSArray).filtered(using: predicate) as! [[String]]
print(filterArr)
}
Don't use Objective-C stuff like NSPredicate and NSArray when you don't have to. Use Swift's filter and map.
From what you described in the comments, you want to keep only the inner arrays that contains the searched text. You can do this:
filterArr = actors.filter { $0.contains(searchController.searchBar.text!) }
If you want to keep only the inner arrays that contains items that contains the search text, do this:
filterArr = actors.filter { $0.contains { $0.contains(searchController.searchBar.text!) } }

Userdefault Array Saving for Items on Plist Accordingly

Okay so I just got a quick question if this is possible or not. I got a array of dictionaries on my plist and it shows on viewcontroller 1 in a list of cells about 800 items. If user hits one of these cells and goes to Viewcontroller 2 can I make like a array of userdefaults associated with each cell? For example if I have a list of fruits like Apple, Banana, Kiwi. User hits Apple and on the userdefaults it knows it hit the first cell and put a userdefault value of 1. What I am trying to do is give a userdefault value of each cell so that user can save these items to their favorite list and I just use userdefault to save that one specific item to favorite list. Cause currently when I use the userdefault it saves all the items at once because if I put the boolean to true for one item it does it for all items since each item on the plist cell uses the same userdefault key. Like on my viewcontroller 2 is set up
var isFavorite = UserDefaults.standard.bool(forKey: "isFavorite")
#IBAction func addToFav(_ sender: UIButton){
isFavorite = !isFavorite
UserDefaults.standard.set(isFavorite, forKey: "isFavorite")
UpdateButtonAppearance()
}
func UpdateButtonAppearance(){
if isFavorite{
let image = UIImage(named: "addFav")
favButton.setImage(image, for: .normal)
} else{
let image = UIImage(named: "addFavFilled")
favButton.setImage(image, for: .normal)
}
}
So to avoid saving all the list the same way I was wondering how can I save each cell individually using the userdefault method?
Create a dictionary with keys for all of the favorites, such as "Apple", "Banana", "Kiwi", and "true" as the value. UserDefaults should happily save a dictionary for you if it understands both the keys and values, ie they are both strings.
let kFavoritesKey = "Favorites"
class FavoritesStorage {
// Instantiate with some default favorites
var dict = ["Apple":"true", "Banana":"true", "Kiwi":"true"]
init() {
loadFromDisk()
}
func favor(_ key: String) {
dict[key] = "true"
saveToDisk()
}
func unFavor(_ key: String) {
dict[key] = nil
saveToDisk()
}
func isFavored(_ key : String) -> Bool {
return dict[key] != nil
}
func toggle(_ key: String) {
let favored = isFavored(key)
if favored {
unFavor(key)
} else {
favor(key)
}
}
// These can be changed to save/read somewhere else besides user defaults
private func saveToDisk() {
UserDefaults.standard.set(dict, forKey: kFavoritesKey)
}
private func loadFromDisk() {
if let diskVersion = UserDefaults.standard.object(forKey: kFavoritesKey) as? [String: String] {
dict = diskVersion
}
}
}
let favorites = FavoritesStorage()
var isAppleFavorited = favorites.isFavored("Apple") // true
var isMangoFavorited = favorites.isFavored("Mango") // false
if !isMangoFavorited {
favorites.favor("Mango")
}
isMangoFavorited = favorites.isFavored("Mango") // true
favorites.toggle("Apple")
isAppleFavorited = favorites.isFavored("Apple") // false
if let diskVersion = UserDefaults.standard.object(forKey: kFavoritesKey) {
print(diskVersion)
}

Mutating property by its name

With the help of Reflection API I'm getting the properties list for my types.
func inspectedProperties(ignored: [String] = []) -> [Property] {
var properties = [String]()
for child in self.children() {
guard let label = child.label else {
continue
}
properties += [label]
}
return properties.filter { !ignored.contains($0) }
}
This function returns me the names for all properties.
Now I want to mutate a certain property just by knowing its name.
class Fruit {
private dynamic var name = "Apple"
}
If I call Fruit().inspectedProperties() I'll get the following array ["name"].
But is it possible to mutate the variable named "name"?
OK, I found a very simple solution but it is not flexible. Actually, you can use KVO to mutate your data types. For this purpose your models should be subclasses of NSObject to enable KVO features and variables marked as dynamic.
P.S.
typealias Property = String
class Fruit: NSObject {
private dynamic var name = "Apple"
}
Then there is the mutating function.
func mutateProperty<T>(property: Property) -> T -> () {
return { value in
let filtered = self.children().filter { label, value in
if let label = label where label == property {
return true
}
return false
}
guard let child = filtered.first else {
return
}
if let object = self as? NSObject where child.value is T {
object.setValue(value as? AnyObject, forKey: property)
}
}
}
Then try out:
let fruit = Fruit()
fruit.mutateProperty("name")("Google")
print(fruit.name) // "Google"
It works, but if you want to work with value types rather than with reference ones it won't work. There might be some low level solution but I'm not familiar with one. If anyone knows how to, please leave your answer here! :)