XCUIElementQuery that will match all instances of a custom class - ios

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])

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

Changing filter on Realm notification

I wonder if it is possible to use the same notificationblock on a resultset but change the filter? For example: I have two queries, one with isDelivered = true and one where isDelivered = false. I would like to have one Resultset with different filters and then switch the resultset depending on if I want to see delivered or undelivered items. Is that possible or do I need to create two notificationblock for this?
It's not possible to retroactively change the predicate query used to create a Realm Results object. But it is possible to attach the same notification block to two separate Results instances in order to share the handling logic.
let notificationBlock: ((RealmCollectionChange) -> Void) = { changes in
// Perform common update logic in here
}
let deliveredObjects = realm.objects(MyObject.self).filter("isDelivered = true")
let delieveredNotificationToken = deliveredObjects.addNotificationBlock(notificationBlock)
let undeliveredObjects = realm.objects(MyObject.self).filter("isDelivered = false")
let undelieveredNotificationToken = undeliveredObjects.addNotificationBlock(notificationBlock)

predicate from js to objective C

I had a .js file for my screenshot automation with Instruments.app where I was looking up a cell with the following predicate:
var classScheduleCell = classScheduleTableView.cells().firstWithPredicate("isEnabled == 1 && NONE staticTexts.value BEGINSWITH 'No classes'").withValueForKey(1, "isVisible");
I want to translate that predicate to an objective C UI test, as the ruby scripts I was using for the screenshots now uses UI testing instead of Instruments. Using the same predicate fails
XCUIElement *firstCell = [classScheduleTableView.cells elementMatchingPredicate:[NSPredicate predicateWithFormat:#"isEnabled == 1 && NONE staticTexts.value BEGINSWITH 'No classes'"]];
Looks like I can make the first part of the predicate work changing
isEnabled == 1
for
enabled == true
Any ideas on how to make the other part work?
I found a solution for this, though is not the most elegant. I couldn't find a way to make a predicate to work as the one I had in UI Automation, so I used a couple of for loops to check the value of the cell labels.
NSPredicate *enabledCellsPredicate = [NSPredicate predicateWithFormat:#"enabled == true "];
XCUIElementQuery *enabledCellsQuery = [classScheduleTableView.cells matchingPredicate:enabledCellsPredicate];
int cellCount = enabledCellsQuery.count;
for (int i = 0; i < cellCount; i++) {
XCUIElement *cellElement = [enabledCellsQuery elementBoundByIndex:i];
XCUIElementQuery *cellStaticTextsQuery = cellElement.staticTexts;
int textCount = cellStaticTextsQuery.count;
BOOL foundNoClasses = NO;
for (int j = 0; j < textCount; j++) {
XCUIElement *textElement = [cellStaticTextsQuery elementBoundByIndex:j];
if (textElement.value && [textElement.value rangeOfString:NSLocalizedString(#"No classes", nil) options:NSCaseInsensitiveSearch].location != NSNotFound) {
foundNoClasses = YES;
break;
}
}
if (foundNoClasses == NO) {
[cellElement tap];
break;
}
}
Thanks #joe-masilotti for your help anyway.
I don't believe your exact predicate is possible with UI Testing.
UI Testings vs UI Automation
Matching predicates with UI Testing (UIT) behaves a little differently than UI Automation (UIA) did. UIA had more access to the actual UIKit elements. UIT is a more black-box approach, only being able to interact with elements via the accessibility APIs.
NOT Predicate
Breaking down your query I'm assuming the second part is trying to find the first cell not titled 'No classes'. First, let's just match staticTexts.
let predicate = NSPredicate(format: "NOT label BEGINSWITH 'No classes'")
let firstCell = app.staticTexts.elementMatchingPredicate(predicate)
XCTAssert(firstCell.exists)
firstCell.tap() // UI Testing Failure: Multiple matches found
As noted in the comment, trying to tap the "first" cell raises an exception. This is because there is more than one match for a text label that starts with "No classes".
NONE Predicate
Using the NONE operator of NSPredicate leads us down a different path.
let predicate = NSPredicate(format: "NONE label BEGINSWITH 'No classes'")
let firstCell = app.staticTexts.elementMatchingPredicate(predicate)
XCTAssert(firstCell.exists)// XCTAssertTrue failed: throwing "The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet." -
This approach can't even find the cell. This is because the elementMatchingPredicate() expects the predict to return a single instance, not an array or set. I couldn't figure out how to make the query select the first element. And once you get an XCUIElement there are no ways to farther restrict it.
Different Approach
That said I suggest you take a slightly different approach for your test. If your tests are deterministic, and they should be, just tap the first known cell. This means you don't have to worry about ambiguous matchers nor chaining predicates.
let firstCell = app.staticTexts["Biology"]
XCTAssert(firstCell.exists)
firstCell.tap()

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
}

Creating an NSPredicate with a variable

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.

Resources