predicate from js to objective C - ios

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

Related

Find multiple elements that are equal to a condition in array?

I am a new to SwiftUI, so I apologise if this a dumb question but I came upon a problem that I cant find a solution to.
I am searching something similar to this code below, but instead of finding the first element I would like to retrieve all elements that have the status == 0.
if let new = array.first(where: {$0.status == 0}) {
// do something with foo
} else {
// item could not be found
}
Swift collections have a filter method which allows you to select all elements that match a given condition. You already have a matching condition, so to adapt your example text you'd write:
let newItems = array.filter { $0.status == 0 }

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

Randomly Selecting In EarlGrey

I was writing pretty complicated UI tests using XCTest, and recently switched to EarlGrey, because it's so much faster and way more reliable - Tests aren't randomly failing on a build server, and the test suite could take up to a half hour to run!
One thing that I haven't been able to do in EarlGrey, that I could do in XCTest, was randomly select an element.
For example, on a calendar collectionView, I could query for all collectionViewCells with 'identifier' using an NSPredicate, and then randomly select a day using [XCUIElementQuery count] to get an index, and then tap.
For now, I'm going to hard code it, but I would love to randomize date selection so that I don't have to rewrite tests if we change app code.
Please let me know if I can elaborate, looking forward to solving this!
Step 1 write a matcher that can count elements matching a given matcher using GREYElementMatcherBlock:
- (NSUInteger)elementCountMatchingMatcher:(id<GREYMatcher>)matcher {
__block NSUInteger count = 0;
GREYElementMatcherBlock *countMatcher = [GREYElementMatcherBlock matcherWithMatchesBlock:^BOOL(id element) {
if ([matcher matches:element]) {
count += 1;
}
return NO; // return NO so EarlGrey continues to search.
} descriptionBlock:^(id<GREYDescription> description) {
// Pass
}];
NSError *unused;
[[EarlGrey selectElementWithMatcher:countMatcher] assertWithMatcher:grey_notNil() error:&unused];
return count;
}
Step 2 Use % to select a random index
NSUInteger randomIndex = arc4random() % count;
Step 3 Finally use atIndex: to select that random element and perform action/assertion on it.
// Count all UIView's
NSUInteger count = [self elementCountMatchingMatcher:grey_kindOfClass([UIView class])];
// Find a random index.
NSUInteger randIndex = arc4random() % count;
// Tap the random UIView
[[[EarlGrey selectElementWithMatcher:grey_kindOfClass([UIView class])]
atIndex:randIndex]
performAction:grey_tap()];

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.

How to use autocorrection and shortcut list in iOS8 custom keyboard?

I want to use the autocorrection and shortcut list like default English keyboard with my custom keyboard.
I check the in keyboard document but don't know how to use it.
In keyboard documentation.
Every custom keyboard (independent of the value of its RequestsOpenAccess key) has access to a basic autocorrection lexicon through the UILexicon class. Make use of this class, along with a lexicon of your own design, to provide suggestions and autocorrections as users are entering text. The UILexicon object contains words from various sources, including:
Unpaired first names and last names from the user’s Address Book database
Text shortcuts defined in the Settings > General > Keyboard > Shortcuts list
A common words dictionary
How to access shortcut list and input from our dictionary in Objective-C?
How to use UILexicon with requestSupplementaryLexiconWithCompletion?
Implementing the lexicon would look pretty much like this:
Use requestSupplementaryLexiconWithCompletion() to get the lexicon upon launch once.
Each type text is inputted add it to a NSString (tracking the current word)
When user presses space (end of curent word) check the string against the lexicon
If it's a match count the number of characters and delete that number of characters
Input the suggestion suggested by the lexicon
Clear the string and start again
Additionally you could also use UITextChecker to offer more advanced auto-correct features.
Code (in Objective-C, this may not be 100% accurate I wrote in SO while on the bus but it should do):
UILexicon *lexicon;
NSString *currentString;
-(void)viewDidLoad {
[self requestSupplementaryLexiconWithCompletion:^(UILexicon *receivedLexicon) {
self.lexicon = receivedLexicon;
}];
}
-(IBAction)myTypingAction:(UIButton *)sender {
[documentProxy insertText:sender.title];
[currentString stringByAppendingString:sender.title];
}
-(IBAction)space {
[documentProxy insertText:#" "];
for (UILexiconEntry *lexiconEntry in lexicon.entries) {
if (lexiconEntry.userInput isEqualToString:currentString) {
for (int i = 0; currentString.length >=i ; i++) {
[documentProxy deleteTextBackwards];
}
[documentProxy insertText:lexiconEntry.documentText];
currentString = #"";
}
}
}
Feel free to comment if you have any more questions.
Source: Personal experience with iOS 8 keyboards and UILexicon
With regards to auto-correction, I was able to add it using link. Here's the code snippet I used from the link:
UITextChecker *checker = [[UITextChecker alloc] init];
NSRange checkRange = NSMakeRange(0, self.txView.text.length);
NSRange misspelledRange = [checker rangeOfMisspelledWordInString:self.txView.text
range:checkRange
startingAt:checkRange.location
wrap:NO
language:#"en_US"];
NSArray *arrGuessed = [checker guessesForWordRange:misspelledRange inString:self.txView.text language:#"en_US"];
self.txView.text = [self.txView.text stringByReplacingCharactersInRange:misspelledRange
withString:[arrGuessed objectAtIndex:0]];
The full documentation from Apple can be found here.
Although I have not personally tried creating a custom keyboard, I am basing this answer on what I can see in the documentation.
In your keyboard, create a property called entries of type [AnyObject] (Array of AnyObjects).
In your init method, or wherever you create the keyboard, call this method:
requestSupplementaryLexiconWithCompletion(completionHandler: {
lexicon in
self.entries = lexicon.entries
})
I suspect that entries is actually an array of Strings or NSStrings, but it could be a dictionary or some other type. When testing this out, try figuring out what type is actually contained in entries before figuring out your logic.
I do not believe there is a way to get Apple's default autocorrect options currently. However, this WWDC talk gives insight about how they made autocorrect work in the original iPhone OS (around the 30 minute mark).
He mentions using a binary search of the array, which leads me to believe that this array is sorted. Of course, much could have changed since the first iPhone came out...
Good luck figuring out this new API!
This is the way you can actually access Lexicon words:
[self requestSupplementaryLexiconWithCompletion:^(UILexicon *receivedLexicon) {
self.lexicon = receivedLexicon;
for (UILexiconEntry *word in self.lexicon.entries) {
// Text to be inserted into a text input object by a custom keyboard, corresponding to the userInput value in the same lexicon entry.
NSLog(#"%#",word.documentText);
// Text to match, during user input, to provide appropriate output to a text document from the documentText value in the same lexicon entry.
NSLog(#"%#",word.userInput);
}
}];
Rachits answer above in swift 4. Works with iOS 12
I have this helper to check wether the current string to be tested by UITextChecker is not a space
func validate(string: String?) -> Bool {
guard let text = string,
!text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty else {
return false
}
return true
}
The text checker is then in my "Spacebar", "Spacebar double tapped" & "return" methods. The example below is in my "Space" method
let textChecker = UITextChecker()
let currentString = self.textDocumentProxy.documentContextBeforeInput
if validate(string: currentString) {
let charSet = NSCharacterSet.whitespacesAndNewlines
let components = currentString?.components(separatedBy: charSet)
let lastWord = components?.last
let checkRange = NSMakeRange(0, lastWord?.count ?? 0)
let misspelledRange = textChecker.rangeOfMisspelledWord(in: lastWord!, range: checkRange, startingAt: checkRange.location, wrap: false, language: "en_US")
if misspelledRange.length != 0 {
let guessedWord: Array = textChecker.guesses(forWordRange: misspelledRange, in: lastWord!, language: "en_US")!
if guessedWord.count > 0 {
var i = 0
while (lastWord?.length)! > i {
textDocumentProxy.deleteBackward()
i += 1
}
self.textDocumentProxy.insertText(guessedWord[0])
}
}
}
self.textDocumentProxy.insertText(" ")
I had to make two changes to Rachits code. First to validate the currentString since it throws an exception if you press space bar twice. And second to check if the misspelled range is not 0 because that was also throwing an exception which I am yet to figure out why. But this works for me now as is.
Every custom keyboard (independent of the value of its RequestsOpenAccess key) has access to a basic autocorrection lexicon through the UILexicon class. Make use of this class, along with a lexicon of your own design, to provide suggestions and autocorrections as users are entering text. The UILexicon object contains words from various sources, including:
Unpaired first names and last names from the user’s Address Book database
Text shortcuts defined in the Settings > General > Keyboard > Shortcuts list
A common words dictionary that includes the names of Apple products
In case anyone is still looking into this, I found a really nice C++ predictive text library called Presage. It seems to do a good job based on the demo but I'm having a lot of trouble trying to integrate it as a library (see my question here).
Let me know if anyone has any ideas, very interested in getting this working!
Actually, UILexicon is just a way to get some user-specific words that your spellchecking system should't try to fix. Probably, the most common way to use it is to fill out UITextChecker's list of ignored words.
let lexicon: UILexicon = ...
let checker: UITextChecker = ...
for entry in lexicon.entries {
if entry.documentText == entry.userInput {
checker.ignoreWord(entry.documentText)
}
}
Additionally, UILexicon can be used as source of autoreplaced shortcuts like ("omw" = "On my way!"), but it is not autocorrection in terms of spelling.
You can use below logic for AutoCorrect & it will also work in iOS 10
-(void)didClickAtAlphaNumericKeyboardKey:(NSString *)value {
if ([value isEqualToString:#" "]) {
UITextChecker *checker = [[UITextChecker alloc] init];
currentString = self.textDocumentProxy.documentContextBeforeInput;
NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSArray *components = [currentString componentsSeparatedByCharactersInSet:charSet];
NSString *lastWord = components.lastObject;
NSRange checkRange = NSMakeRange(0, lastWord.length);
NSRange misspelledRange = [checker rangeOfMisspelledWordInString:lastWord
range:checkRange
startingAt:checkRange.location
wrap:NO
language:#"en_US"];
NSArray *guessedWord = [checker guessesForWordRange:misspelledRange inString:lastWord language:#"en_US"];
if (guessedWord && guessedWord.count > 0) {
for (int i = 0; lastWord.length >i ; i++) {
[self.textDocumentProxy deleteBackward];
}
[self.textDocumentProxy insertText:[guessedWord objectAtIndex:0]];
}
}
[self.textDocumentProxy insertText:value];
}

Resources