Searching by object property with UISearchController? - ios

In my updateSearchResultsForSearchController method, I have a searchPredicate that I use to find matches, however, I want to learn how to use it with an Object's property.
I have an object called Product that has a String property called title which I would like to use as my search parameter. So far, this is what my search function looks like:
func updateSearchResultsForSearchController(searchController: UISearchController) {
productSearchResults.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (orderGuideItemsList as NSArray).filteredArrayUsingPredicate(searchPredicate)
productSearchResults = array as! [Product]
self.tableView.reloadData()
}
where orderGuideItemsList is an array of Product objects
So is there a way for me to search based on Proudct's title property? It doesn't have to use a predicate but that's what I have from a previous tutorial.
Thanks!

You can update your predicate to
let searchPredicate = NSPredicate(format: "SELF.title CONTAINS[c] %#", searchController.searchBar.text)
This should work fine.

Related

Searching with NSFetchResult Controller

I've been looking on how to search using a NSFetchResultController but most post I came across are from 2 years ago. Okay I'm trying to filter the objects, which in my case are Pokemon. Here is my function I'm using.
func attemptPokemonFetch(generation: String = "1", name: String = "") {
let context = coreData.persistentContainer.viewContext
let request: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
let sortByName = NSSortDescriptor(key: "id", ascending: true)
request.sortDescriptors = [sortByName]
request.predicate = NSPredicate(format: "generation = %#", generation)
if !name.isEmpty {
request.predicate = NSPredicate(format: "name CONTAINS[cd] %#", name)
}
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
}
catch let err {
print(err)
}
}
I call this function in view did load, search bar did change text and when the segment changes index value. I know how to filter the Pokemon and use the searching. However the problem I come across is when I begin searching. When searching for a Pokemon in a specific generation other Pokemon from another generation will also appear. So I'll be at index 2 which is for generation 2 Pokemon and when searching, other Pokemon from different generation will be appearing. I'm not sure if its how I initialize the context. This is how my search bar function look like.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar == pokemonSearchBar {
let text = pokemonSearchBar.text!
self.attemptPokemonFetch(name: text)
collectionView.reloadData()
}
}
Another Example: Lets say I have 3 types of people some are skinny, normal and fat. When I'm at the index for skinny people I only want to search for people that are skinny not all the people. So what would I need to do to achieve this type of behavior.
Would really appreciate any help. :)
In your code
request.predicate = NSPredicate(format: "generation = %#", generation)
if !name.isEmpty {
request.predicate = NSPredicate(format: "name CONTAINS[cd] %#", name)
}
the second assignment replaces the previously assigned predicate.
What you probably want is
if name.isEmpty {
request.predicate = NSPredicate(format: "generation = %#", generation)
} else {
request.predicate = NSPredicate(format: "generation = %# AND name CONTAINS[cd] %#", generation, name)
}
A more flexible approach is to use NSCompoundPredicate:
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "generation = %#", generation))
if !name.isEmpty {
predicates.append(NSPredicate(format: "name CONTAINS[cd] %#", name))
}
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)

NSPredicate Formatting Issue

I'm creating an iOS app where I want the user to be able to search through a UITableView and display results based on a variety of different inputs. I'm using an NSPredicate with multiple conditions separated by "OR" to do this, but for some reason it's only searching with the first condition and none of the others.
Here's how my NSPredicate is formatted:
let searchPredicate = NSPredicate(format: "(SELF.firstName CONTAINS[c]
%#) OR (SELF.lastName CONTAINS[c] %#) OR (SELF.major CONTAINS[c] %#) OR
(SELF.year CONTAINS[c] %#) OR (SELF.gpa CONTAINS[c] %#)",
searchController.searchBar.text!)
My parameters are firstName, lastName, major, year, and gpa. When I type in anything besides the first name in the search bar, no results show up. But typing in a first name does indeed return matches. Why is this happening? Is my NSPredicate formatted incorrectly?
You have five instances of the %# format specifier in your predicate so you need to pass five values. You are only passing one.
You need to repeat searchController.searchBar.text! five times. BTW - assign that to a safely unwrapped variable and repeat that variable five times. It will be less typing.
if let srch = searchController.searchBar.text {
let searchPredicate = NSPredicate(format: "(SELF.firstName CONTAINS[c] %#) OR
(SELF.lastName CONTAINS[c] %#) OR
(SELF.major CONTAINS[c] %#) OR
(SELF.year CONTAINS[c] %#) OR
(SELF.gpa CONTAINS[c] %#)", srch, srch, srch, srch, srch)
...
}
if let text = searchController.searchBar.text, text.characters.count > 0{
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "self.firstName CONTAINS[c]",text))
predicates.append(NSPredicate(format: "self.lastName CONTAINS[c]",text))
predicates.append(NSPredicate(format: "self.major CONTAINS[c]",text))
predicates.append(NSPredicate(format: "self.year CONTAINS[c]",text))
predicates.append(NSPredicate(format: "self.gpa CONTAINS[c]",text))
let searchPredicate:NSPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
}
Not tested but this should work and is probably the simplest syntax for Swift if all your subpredicates are the same:
if let text = searchController.searchBar.text {
let myCompondPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: ["firstName", "lastName", "major", "year", "gpa"].map {
NSPredicate(format: "%K CONTAINS[c] %#", $0, text)
})
// Do something with myCompondPredicate here..
}
For a simple, static, predicate like this, I would opt for a native Swift approach:
import Foundation
struct Student {
let firstName: String
let lastName: String
let major: String
let gpa: Double
}
extension Student {
func anyFields(contain searchString: String) -> Bool {
return firstName.contains(searchString)
|| lastName.contains(searchString)
|| major.contains(searchString)
|| String(gpa).contains(searchString)
}
// Alternate implementation:
/*
func anyFields(contain searchString: String) -> Bool {
return [firstName, lastName, major, String(gpa)].contains(where: { field in
field.contains(searchString)
})
} */
}
let matchingStudents = students.filter(Student.anyFields(contain:))

Searching string in NSArray of custom object using NSPredicate

I have two classes like below:
class City : NSObject{
var header:String? = nil
var areas:NSMutableArray? = nil //Contain array of Area
//Return array of City objects
class func generate(cityCount:NSInteger) -> NSMutableArray{...}
}
and
class Area : NSObject{
var title:String? = nil
var address:String? = nil
}
//Return array of Area objects
class func generate(areaCount:NSInteger) -> NSMutableArray {...}
Now, I have Array of City like this declared in my viewcontroller:
var cities = City.generate(200)
and when I search the header inside using NSPredicate it work perfectly
let pred = NSPredicate(format: "SELF.header CONTAINS %#",searchString)
let filteredCities = self.cities.filteredArrayUsingPredicate(pred)
But when I search the cities->areas->address (I want to search address). It is not working. It is always return 0 object. Here what I am trying:
let pred = NSPredicate(format: "SELF.areas.address CONTAINS %#",searchString) //name
let filteredCities = (self.cities as NSArray).filteredArrayUsingPredicate(pred)
EDIT
I need only the area object that contain matching address.
I have tried:
let pred = NSPredicate(format: "ANY areas.address CONTAINS %#",searchString)
This is giving the City object with all area object.
Thanks in advance.
Have try like this
let pred = NSPredicate(format: "ANY areas.address CONTAINS %#",searchString)
Try this bro,
let pred = NSPredicate(format: "SELF contains[c] %#",searchString)
let filteredCities = self.area.filteredArrayUsingPredicate(pred)

Search Bar in a TableView with Core Data (using Swift)

I am making a TableViewController with Core Data. In fact the users can add new items to the Table View and these items are saved in Core Data. Everything has worked fine but when I add a Search Bar I'm blocked.
I added a Search Bar but this function bellow create an error when I run the app in the simulator.
func updateSearchResultsForSearchController(searchController: UISearchController,)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (series as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
The error I get is " terminating with uncaught exception of type NSException "
PS : I use Xcode 6.3.2 and all the code is in Swift.
Try the following:
func updateSearchResultsForSearchController(searchController: UISearchController) {
var request = NSFetchRequest(entityName: "Serie")
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF.infos CONTAINS[c] %#", searchController.searchBar.text)
let array = (series as NSArray).filteredArrayUsingPredicate(searchPredicate)
for item in array
{
let infoString = item.infos
filteredTableData.append(infoString)
}
self.tableView.reloadData()
}

UISearchController searching two arrays

How can I use UISearchController to search two different arrays and create two new filtered arrays based on that search? The parameters should only require that one array contain the string, then if one of the arrays have it, both indexes should be added. As of now, I use
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredKeys.removeAll(keepCapacity: false)
filteredValues.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (values as NSArray).filteredArrayUsingPredicate(searchPredicate)
println(array)
let arrayTwo = (keys as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredKeys = arrayTwo as! [String]
filteredValues = array as! [String]
self.tableView.reloadData()
}
However, this is very bad and often crashes the app due to a difference in the count of the filtered arrays. Please help, I have been stuck on this for a while.
The search should look at the content:
arrayOne = ["title1", "title2", "title3]
arrayTwo = ["message1", "message2", "message3]
and should filter the arrays based on the search title1 as
arrayOneFiltered = ["title1"]
arrayTwo = ["message1"]
Could someone help me with the predicate string and the filtering please?
Again, this is using a UISearchController in a TableViewController
Since Dictionary or NSDictionary already provides key-value pair type mechanism, you can wrap your related values in a dictionary like this:
var dictionary = ["title_key": "title something", "message_key": "message text"]
And then you can make an array of dictionaries.
var masterArrayOfDictionaries = [ dictionary , ["title_key": "title something 2", "message_key": "message text 2"]]
You can make dictionary at runtime and append to array:
var dictionary3 = ["title_key": "title something 3", "message_key": "message text 3"]
masterArrayOfDictionaries.append(dictionary3)
Now your predicate should look like:
//You want to search on the 'message_key' so put it in the predicate
let searchPredicate = NSPredicate(format: "message_key CONTAINS[c] %#", searchController.searchBar.text)
Now apply the predicate as follow:
let arrayFiltered = (masterArrayOfDictionaries as NSArray).filteredArrayUsingPredicate(searchPredicate)
This will give you filtered array having the dictionaries that contains searched text against their message_key

Resources