Creating an NSPredicate with a variable - ios

I'm using Core Data to store an object model consisting of several entities, but the relevant one is this:
Entity:
Stretch
Relevant Attributes:
equipmentUsed,
muscleGroup
I have a scene in storyboard with a UISegmentedControl I use to choose to select equipmentUsed.
// Creates a variable which changes the selectedEquipment type based on which
// selectedSegmentIndex is clicked
var stretches: [Stretch] = [] // List of stretches
var selectedEquipment : NSString? // Equipment selected from UISegmentedControl
#IBAction func selectEquipment(sender: UISegmentedControl) {
if equipmentSelector.selectedSegmentIndex == 0 {
self.selectedEquipment = "roller"
}
if equipmentSelector.selectedSegmentIndex == 1 {
self.selectedEquipment = "none"
}
}
Below the UISegmentedControl, I have static cells that I use to toggle various muscle groups. Every example of filtering using predicates dumps all the data on a TableView, then filters afterwards. I'm trying to pick the key paths for a predicate and put them in an array.
I'm trying to create predicates that select a particular piece of equipment (ex: None or Foam Roller) AND a particular muscle group. I'm using the selectedEquipment string from earlier as a variable in the predicate below:
var shoulderPredicate = NSPredicate(format: "stretch.equipmentUsed contains[c] $selectedEquipment AND (stretch.muscleGroup contains[c] %#", "shoulders")
I'm running into trouble creating an array from shoulderPredicate. Here's my code for creating array:
#IBAction func testSearchStretches(sender: AnyObject) {
println("testSearchStretches clicked")
var stretchList = (stretches as NSArray).filteredArrayUsingPredicate(shouldersPredicate)
for stretch in stretchList {
println(stretch)
}
}
I can see the testSearchStretches IBAction prints "testStretches clicked" in the console, but it doesn't print stretchList, but I know there's a Stretch that matches the predicate. I believe the predicate I set up is case-insensitive, so I don't think that's the problem.
Once I get the array figured out, I'm going to display the results on another screen. For now, I'm just trying to get the predicate working to create the stretchList array. I'm new to programming and I'm trying to move beyond tutorials to create something on my own. If you made to here, thanks for reading and I greatly appreciate your help.

There are two problems with your predicate:
You must not include the entity name in the key paths.
$selectedEquipment in a predicate format string does not insert or interpolate
the value of the selectedEquipment variable. $VAR in a predicate can be used
with the predicateWithSubstitutionVariables() function, but I assume that is
not what you want here.
Your predicate should probably be
NSPredicate(format: "equipmentUsed contains[c] %# AND muscleGroup contains[c] %#",
selectedEquipment, "shoulders")
assuming that "equipmentUsed" and "muscleGroup" are String attributes.

Related

Filtering a SectionedFetchRequest in SwiftUI on iOS15

I am trying to implement the new iOS 15 SectionedFetchRequest with Core Data. This allows you to change the predicates and sort descriptors at runtime.
My model has a Group, which can contain many Items.
I declare the fetch request like this, then assign to it in the view's init method, so that I can apply a custom NSPredicate depending on where the view is being shown.
#SectionedFetchRequest private var mySections: SectionedFetchResults<String, Item>
The they are displayed in a ForEach loop, similar to the code from the WWDC 2021 session.
var body: some View {
// Other code
List {
ForEach(mySections) { section in
Section(header: Text(section.id) {
ForEach(section) { item in
Text(item.name) // Just an example
}
}
}
}
}
I am also implementing the .searchable view modifier, to provide search functionality. This is bound to a string variable like this.
#State private var searchText: String = ""
Then in the body property I use .searchable($searchText) on the bottom of the list.
This all works without issues. However, the problem comes when I try to use the search text to filter my results at runtime. I tried using a dynamic predicate instead and changing the fetch request predicate (compounded with the one from initialisation), but this proved very buggy (maintaining the search state when the view popped to a detail view was very difficult and led to details views popping unexpectedly).
So, I decided to filter the fetch request in runtime with standard Swift code. I have done this before easily with a one-dimensional array. However, SectionedFetchRequest is not just a two-dimensional array; it is a generic struct with Section and Element types and the section has an important id property (used to define the section heading).
Standard two dimensional filtering (such as this answer does not work, as the types are lost and you merely get a two-dimensional array.
I can easily get the sections that have an item that fits the search, but this returns all of the items in the section (rather than just the ones that fit the search):
mySections.filter { section in
section.contains(where: checkItemMethod())
}
I tried to then make my own type that conformed to RandomAccessCollection (required by the ForEach) that I could populate recursively, but this seems very complex as there are so many "sub-protocols" to conform to.
Is there a way to easily filter a SectionedFetchRequest based on the inner objects, and only returning sections containing objects that match?
Thanks
EDIT
Here is my view init code. The SelectedSort type is straight out of the WWDC video. I just persist the by and order values in User Defaults.
init(filterGroup: DrugViewFilterGroup) {
self.filterGroup = filterGroup
// This relates to retrieving persisted value of the SelectedSort type demonstrated in WWDC
let sortBy = UserDefaults.standard.integer(forKey: "sortBy")
let sortOrder = UserDefaults.standard.integer(forKey: "sortOrder")
let selectedSort = SelectedSort(by: sortBy, order: sortOrder)
let sectionIdentifier = sorts[selectedSort.index].section
let sortDescriptors = sorts[selectedSort.index].descriptors
// Create sectioned fetch request
let sfr = SectionedFetchRequest<String, Item>(sectionIdentifier: sectionIdentifier, sortDescriptors: sortDescriptors, predicate: filterGroupPredicate(for: filterGroup), animation: .spring())
// Assign fetch request to the #SectionedFetchRequest property of the view
self._mySections = sfr
}
This all works just fine. The filterGroupPredicate(for:) method just returns an NSPredicate to show a subset of data.
The issue is with implementing the search bar with the .searchable modifier. This is bound to #State private var searchText: String.
Here is an example function filtering the results by the search text:
private func getFilteredSections() -> [SectionedFetchResults<String, Item>.Section] {
let cleanedFilter = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !cleanedFilter.isEmpty else { return mySections.map { $0 } }
func checkItem(_ item: Item) -> Bool {
return item.name.localizedCaseInsensitiveContains(cleanedFilter)
}
return mySections.filter { section in
section.contains(where: checkItem)
}
This works, but the problem is it only returns the "top level" i.e. the sections themselves. If a section contains a single item meeting the search criteria, all items in the section will still be returned.
Filtering it like a multidimensional array (as noted in original question), results in a simple 2D array with no section information to display in the nested ForEach, and thus the functionality of the SectionedFetchRequest is lost.
I tried setting the fetch request predicate (to a compound predicate based on the one set in init and one for the search text). This works briefly, but as the view is reinitialised on e.g. navigating to a tapped detail view, it results in erratic search bar behaviour.
SectionedFetchResults has a property nsPredicate. If you set that with your desired NSPredicate(format:), the results are updated more or less immediately.
Works with SwiftUI search with something like:
mySection.nsPredicate = NSPredicate(format: "name contains[cd] %#", searchText)
Most of the other code can be erased. You need a little magic to manage displaying the filtered collection.
SectionedFetchResults > nsPredicate in Apple Dev Docs

XCUIElementQuery that will match all instances of a custom class

I replaced all the instances of UISwitch in my app with a custom class - let's call it MySwitch - and as a result the XCUIElementTypeQueryProvider.switches query doesn't contain any results. How can I create a query that does match all descendants that are instances of MySwitch?
Ideally what I want is to be able to call someElement.switches and have it return all instances of UISwitch and MySwitch, or call someElement.mySwitches and have it return all instances of MySwitch.
You’d need a way to uniquely identify your switches since presumably they are now coming back as otherElement and a query for all of those would return a lot of junk (depending on your app). Add an accessibility ID of mySwitch (or anything you'd like) to your switch and you should be able to get them all back with myElement.otherElements[“mySwitch”].
If you need a query that returns both your custom switches (found by an identifier) AND standard switches you're going to have to come up with a compound predicate. I've made a guess at what the second predicate should be (not tested / never written an elementType predicate so you might have to play around a bit), but it'd be something like this:
let mySwitchPredicate = NSPredicate(format: "identifier = %#", "mySwitch")
let standardSwitchPredicate = NSPredicate(format: "elementType = %#", ".switch")
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [mySwitchPredicate, standardSwitchPredicate])

How to group Core Data objects as the data source for a UITableView

In Core Data, I have a large number of "City" objects, each of which has a "country" property.
Rather than have a very long scrollable list of cities, I need to have a separate screen which lists just Countries in a UITableView. The user then selects a country and is taken to a new screen, which has the Cities for that Country.
Note: This is for a game, so the UITableView data source is bound to a SKScene, but I don't think that should make any difference as it works fine for other screens.
I have figured out the detail screen which displays the Cities for a specific country and it works fine in test with hard-coded data.
I am using Swift 5 so my data calls are like this:
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "City")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "country = %#", country)
try fetchedResultsController.performFetch()
However for the other screen, I don't know how to "group" the Countries, which are the property on each City, and make the groupings the data source for the UITableView on the other screen.
a) I could create an array of Countries, but how would I have the fetchedResultsController return the Country data to UITableView?
b) Or, is it possible to group or filter objects within a fetchedResultsController?
c) Or, is it possible to do SQL-like grouping within a fetch request?
Thanks
Thanks #Larme for the link to https://developer.apple.com/documentation/coredata/nsfetchrequest/1506191-propertiestogroupby
So to get this working, Results Container needs to be defined with NSDictionary.
var fetchedResultsController: NSFetchedResultsController<NSDictionary>!
The fetchRequest needs to be set to group and fetch the specific property.
fetchRequest.propertiesToFetch = ["country"]
fetchRequest.propertiesToGroupBy = ["country"]
fetchRequest.resultType = .dictionaryResultType
Then in the methods where we need to access the data:
// cell data
let countryName = (fetchedResultsController.object(at: indexPath) as NSDictionary)["country"]!
cell.textLabel?.text = "\(countryName)"
Thanks for pointing me in the right direction!

how to query the whole Realm database for a specific data and not only from a specific field or attribute

I have a realm database with a table with 5 fields. The fields are "clientName", "occasions" , "venue" , "time" and "date". In the display in uitableview I would be able to display all the data according to the clientName using :
allEvent = realm.objects(AllEventClass.self).sorted(byKeyPath: "clientName", ascending: true)
Now I want to set up a UISearchBar with a function to search the whole database irrespective of fields. No matter what data from any field the user entered into the searchbar, the tableview will show the result in clientName which is related to the searched data entered. For now I can search the database only using the clientName field and the result would of course be the clientName. For instance it would be a problem if I only remember the "date" but I could not remember the clientName associated with that "date". I want to be able to enter the "date" or any data from any other fields and still be given the clientName result associated with it. For now this is the only code available to me.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
allEvent = AllEventClasses?.filter("clientName CONTAINS[cd] %#", searchBar.text!).sorted(byKeyPath: "clientName", ascending: true)
tableView.reloadData()
}
Thank you very much in advance.
The question may be asking one of a couple of questions. Let me address two. For clarity, a Realm Database will contain objects and those objects will have properties (fields). So there isn't really a table with fields (as that's a bit generic) so any queries will be performed against those objects and their properties.
You may be asking how to query across all objects in your database at the same time.
A Realm Result is a homogenous collection type, so you can't store different subclasses of Object in the same Results object - that means you can't perform a query across multiple objects and return the results into one Result object. One simple solution is to query on each object type.
However, I think your actual question is asking how to query across multiple properties within an object. So assuming your object looks like this
class AllEventClass: Object {
#objc dynamic var clientName = ""
#objc dynamic var occassions = ""
#objc dynamic var venue = ""
#objc dynamic var time = ""
#objc dynamic var date = ""
}
and suppose you want to query for any matches in the clientName, occassions and date properties.
let search = stringFromSearchBar
let eventResults = realm.objects(AllEventClass.self).filter("clientName contains[cd] %# or date contains[cd] %# or occassions contains[cd] %#", search, search, search)
let me know if you asked something else and I will update the answer.

How to get specific row data from the CoreData sqlite table using Swift

I am new to iOS development using Swift language.
I am using CoreData in my app as a database option.
I am using NSFetchRequest for fetching records from the table. I can retrieve all records from the table, but I can't retrieve a specific row from the same table.
I have checked for the solutions online and other forums, but I am unable to get a proper solution for this.
I want to implement this in Swift only. I don't want to add the sqlite library or Bridging-wrapper (Objective - C) which will be my last option to implement this.
Any links or tutorials or suggestions will be helpful.
NSFetchRequest also support NSPredicate. With NSPredicate you can choose which exactly rows or row you need from Core Data.
More about NSPredicate - https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSPredicate_Class/
You can fetch a desired row by executing a fetch request, casting it as an array, and simply using subscript to retrieve an index. Or you can narrow down your results using a unique id and a predicate and getting the object at index 0.
if let results = managedObjectContext?.executeFetchRequest(fetchRequest, error: nil) as? [SomeClass] {
let someObject = results[someIndex]
}
As Pavel said, use NSPredicate. Here is a working example from my code, it might help you to get started ;) I added some comments below relevant lines
var req = NSFetchRequest(entityName: "YourDBTable")
// your db-table goes here
req.sortDescriptors = [NSSortDescriptor(key: "timeMillis", ascending: true)]
// if you want to sort the results
let start = NSNumber(longLong:int64StartValue)
// you need to convert your numbers to NSNumber
let end = NSNumber(longLong:int64EndValue)
let pred = NSPredicate(format:"timeMillis>=%# AND timeMillis <=%# AND foreignTableObject=%#",start,end, foreignTableObject)
// if you have relationships, you can even add another NSManagedObject
// from another table as filter (I am doing this with foreignTableObject here)
req.predicate = pred
var fetchedResults = managedContext?.executeFetchRequest(req,error: &error) as! [YourDBTable]?
// YourDBTable-class was created using the Xcode data model -> Editor -> Create NSManagedObject SubClass... - option
if let results = fetchedResults {
// iterate through your results
}

Resources