Another UIMenuController won't display problem - swift

and thanks in advance for your help. I assure you that I've read most everything on here about UIMenuController problems. I really think I've covered it all. Clearly I've missed something.
In a nutshell, I'm trying to replicate the "Replace ..." edit menu behavior (but with my own function different than Replace). (If you're not familiar, when a word is selected, the Replace... option in the edit menu will bring up a second menu which shows possible alternate spellings for the word.)
In a UITextView (sub-classed), I select some text. The default gesture recognizer causes the edit menu to come up with the expected items, including my added "Translate..." option. When I click on "Translate..." in the menu, the menu closes and invokes my selector code. That code changes the menu items to the sub-choices I want. I call
UIMenuController.shared.showMenu(from: self, rect: textBounds).
I see the calls to canPerformAction() to verify that the "sub-menu" items I've added are recognized, but the menu never shows up. The notification for willShowWindowNotification (which occurs when the first menu is opened) does not happen for this sub-menu.
Here is the code:
#objc func translateSelectionMenu()
{
let sharedMC = UIMenuController.shared
// Create menu choices for the translate sub-menu.
let charChoice = UIMenuItem(title: "To Chars", action: #selector(translateChars))
let byteChoice = UIMenuItem(title: "Byte Decimal", action: #selector(translateByte))
let halfChoice = UIMenuItem(title: "2-Byte Decimal", action: #selector(translateHalf))
savedMenuItems = sharedMC.menuItems
sharedMC.menuItems = [charChoice, byteChoice, halfChoice]
... for brevity, I've omitted the code here which determines the bounds of the user's
text selection. The resulting numbers are shown below.
let textBounds = CGRect(x: 114.1, y: 73, width: 48, height: 55)
// let windowBounds = convert(textBounds, to: nil)
// sharedMC.update() not needed
self.becomeFirstResponder() // TextView is already the first responder. This does nothing.
sharedMC.showMenu(from: self, rect: textBounds)
}
Note that the TextView IS and must remain first-responder. (Changing it loses the users selection.) So I've implemented all of this in the subclass of the UITextView that is showing the user's text. I have tried using the UITextView-referenced bounds and the window-referenced bounds but neither works.
If I move one of the end-points of the selected text or just click in the selection, this causes the menu to be shown again, and it has my sub-menu items in it as expected. I know this should work because "Replace..." does it all the time.
Things I've verified:
My sub-class of UITextView is a UIView.
UserInteractionIsEnabled is true (since I can select the text).
There is only one window, but I am calling self.window.makeKeyAndVisible() at the point where canBecomeFirstResonder is called.
I have implemented canBecomeFirstResponder() (returning True). (It is called right before the gesture recognizer brings up the first menu but not after that.)
I do call self.becomeFirstResponder() (even though it already is).
I have implemented canPerformAction(). This is called a lot both with first-menu and sub-menu items. I return True for the items I want to use.
What else? Thanks!!

I asked Apple for help on this. The fix is to add
sharedMC.hideMenu()
right before the call to showMenu().
I think the issue is that my code is not what had presented the Menu originally and so I had to hide it before my code could show it. I note (from notifications) that the menu was not officially "hidden" at all (even though it was no longer visible after pressing my Translate... button).
I also tried just changing the menuItems and calling update(), but that also didn't work, probably again for the same reason.

Related

How to access callout view on clicking on map annotation using swift in xctest

I want to access callout views and do some UIAutomation on those views. I'm able to click on map markers/annotations but not able to access the callout view.
The following code used to tap on the marker:
let marker = app.otherElements.matching(identifier: "mapMarker").element(boundby: 0)
marker.tap();
After this, I'm getting the callout view of the respected marker/annotation.
I need to access that callout.
Please suggest me on this.
You should create a breakpoint after the callout is snown,
then type po print(app.debugDescription) (or simply po app in XCode 11) in lldb in order to view the whole hierarchy of UI elements.
Locate the needed element and access it further in code.
Also, consider rewriting your marker code in a shorter way:
let marker = app.otherElements["mapMarker"].firstMatch
Please notice firstMatch aborts search of elements after it found the first one.
Drop firstMatch, if you want to check that the element is unique
let marker = app.otherElements["mapMarker"]
Same as Smart Monkey said, but to add more code based off the comment from ablarg:
Ex: "mapMarker" being the accessibility ID for the element
let mapMarker = app.maps.otherElements["mapMarker"].firstMatch
let mapMarkerExists = mapMarker.waitForExistence(timeout: 3)
if mapMarkerExists {
mapMarker.tap()
}
waitForExistence(timeout:) returns a bool, so if the element appears before the timeout expires (it finds the element) take action (tap) on the element.
Make sure the element is enabled for accessibility and has the accessibility ID set.

XCode UITest will not give NSTextField keyboard focus

So I have a window appear by clicking on a menu item. Normally the the first textBox would gain immediate keyboard focus, but in the UITest nothing in the window gains keyboard focus (no focus ring). This is problematic becuase it stops me from typing into the textfield with textField.typeText("someText")
I have tried .click() on the window and on the textbox to try and give it focus but nothing seems to bring the window or textbox in focus.
Any help will be greatly appreciated
Example
MainMenu().menuItemForWindow.click() // Opens window
app.windows["windowTitle"].textFields["textBoxId"].click() // Clicks but field does not gain focus
app.windows["windowTitle"].textFields["textBoxId"].typeText("SomeText") // Is not typed into the textField
As a side note, I have verified that all the elements I am querying do actually exist.
EDIT:
I have gotten it to work by literally spamming typeText() until it changes the value of the given text box with something like
if let oldValue = textbox.value as? String {
var newValue: String? = oldValue
while newValue == oldValue {
textbox.typeText("a")
newValue = textbox.value as? String
}
//If we get here then we have edited the text box
textbox.typeText(XCUIDeleteKey) // Get rid of spam text
//TYpe what I want
//...
}
However this method is hacky and I can't really put a time out except from heuristics (roughly 15-30s) so I was hoping someone could help explain a better way of ensuring focus on the textbox or at least an explanation of what I am doing wrong originally. Any help would be greatly appreciated.
Here are two possible ideas for you:
Assign an Accessibility Id to the object you are trying to interact with, and try addressing it via the Accessibility Id. From https://blog.metova.com/guide-xcode-ui-test:
For element identification there is additionally elementMatchingPredicate(NSPredicate) and elementMatchingType(XCUIElementType, identifier: String?).
These are separate from containingPredicate(NSPredicate) and containingType(XCUIElementType, identifier: String?) which are checking the element for items inside it whereas the elementMatching... options are checking the values on the element itself. Finding the correct element will often include combinations of several query attributes.
Enable Accessibility in the System Preferences | Accessibility as described here: https://support.apple.com/en-us/HT204434 and then send keyboard commands to set your focus.

How to set first responder for NSTextView in Swift?

Edit: in a macOS project
I have a simple ViewController which I display as popover on a status item menu app.
I change the text of the view text with a NSTableView, depending of which item is clicked. The code I use is similar to this one:
mainTextField.insertText(newStr, replacementRange: theRange)
(I use insertText for the purpose to have the change recorded in undo manager)
Then I highlight the text:
// create the new NSRange
let range = NSRange(location: startRange, length: newStrLength)
// select the range in field
mainTextField.selectedRange = range
All work fine, except that the text is highlighted but with a light grey instead of the usual sky blue, indicating that the control is not the first responder. And when I click on the field the selection disappear.
Actually I would like that the NSTextView becomes first responder so I can directly copy the selected text.
Edit: if I press Tab key on the keyboard I got the textView to become first responder (and the grey selection becomes standard sky blue).
Corrected Answer
In AppKit, you need:
if mainTextField.acceptsFirstResponder {
mainTextField.window?.makeFirstResponder(mainTextField)
}
In this case, it's probably safe to not check acceptsFirstResponder, but it doesn't hurt either.
Original Answer (UIKit)
You need to call mainTextField.becomeFirstResponder().

XCUITest - neither element nor descendant has keyboard focus

I am testing adding a comment to my app, on my other UI tests I have used the typeText function and everything works perfectly fine. I have also clicked to make Connect hardware keyboard' is off. The app terminates testing and shows the error UI Testing Failure - Neither element nor any descendant has keyboard focus during the addComment method. Any ideas?
func testAddComment() {
let featuredPage = self.app.tabBars["Featured"]
if featuredPage.exists {
featuredPage.tap()
}
sleep(2)
let featuredOffer = self.app.tables.cells.elementBoundByIndex(1)
if featuredOffer.exists {
featuredOffer.tap()
}
sleep(2)
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.tap()
addComment.typeText("Test comment")
}
sleep(2)
let postComment = self.app.buttons["Send"]
if postComment.exists {
postComment.tap()
}
sleep(2)
}
Likely cause:
An extremely common cause of this symptom is to have enabled a parent view of the field in question as an accessibility element. Ensure that none of the parents of the field are enabled as accessibility elements, and the problem is likely to be resolved.
Explanation
This error message can be particularly confusing when you can see that a previous step in the XCUITest has correctly activated a text field or similar as the first responder, the keyboard has appeared, and functionally the field has keyboard focus.
Essentially, the error message is slightly misleading as the tendency is to focus on the keyboard focus part and think that it is telling you that nothing has keyboard focus. Instead, what the message is telling you is that none of the accessibility elements that XCUITest can access have the keyboard focus. And so it can't direct text to the field in question.
Accessibility elements cannot have children that are also accessibility elements. Therefore, if you place a number of fields inside a view, but then mark that parent view as being an accessibility element, all of the field as its subviews become invisible to the accessibility frameworks. You can see and verify this is what is happening using the Accessibility Inspector app on the Mac alongside the simulator: the fields you are trying to target will not be selectable, and instead there will be a parent view that is.
I found the way around this best was to use menuItem and to paste what I wanted to the textField . This was a strange problem as both textField and staticText both didn't allow the test to run functionally. This is an issue I have reported to apple.
Here is my fix
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.tap()
UIPasteboard.generalPasteboard().string = "Test Comment"
let commentTextField = self.app.staticTexts["Add a comment"]
commentTextField.pressForDuration(1.1)
sleep(1)
app.menuItems.elementBoundByIndex(2).tap()
}
You can only use .typeText() on an input element. Static texts are not input elements - they're just text. Your addComment constant is probably the placeholder text within your text field. Tapping on the placeholder text probably activates the text field and keyboard, but you cannot call .typeText() on the placeholder text as the text field is its parent, not its descendant.
You need to call .typeText() on your text field, which should have focus after you tap in it.
let addComment = self.app.textFields["addCommentIdentifier"]
if addComment.exists {
addComment.tap()
addComment.typeText("Test comment")
}
You can try solving this issue by using .doubletap() before you & enter the value in .typeText(string) it worked for me.
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.doubleTap()
addComment.typeText("Test comment")
Turn off I/O -> Keyboard -> Connect Hardware Keyboard in the Simulator menu.
When this option is on, the Simulator doesn't bring up the soft keyboard, so the OS (sometimes) thinks the text field is not focused. Most of my tests have no problem typing in text fields with this option on, but some fail, especially search bars.

Why will my label not change location when trying to add additional text field?

So I have a button on my storyboard. When that button is pressed, I want to move a certain label down a little bit and also add a new text field to the screen. Here is the simplified code:
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.addIngredientLabel.center.y += self.addIngredientLabel.bounds.height
}) { (_) -> Void in
let newIngredientTextField = UITextField(frame: CGRectMake(100, 110, 100, 100))
newIngredientTextField.center.y -= 50
newIngredientTextField.placeholder = "place text here"
newIngredientTextField.contentHorizontalAlignment = .Left
self.view.addSubview(newIngredientTextField)
}
The problem though, is that after the animation is completed, the label jumps back to its original location. It is like the animation is being pre-maturely terminated. The textField gets added and the label moves but it does not stay moved.
I have tried adding the text field at the end of the function instead of in a completion block. No kind of reordering seems to be helping.
I feel like I am not understanding something fundamental to animations. If anyone has any advice I would greatly appreciate it.
Since you're using the storyboard, you are most likely also using auto-layout. The positions of your controls are determined in your visual design (storyboard) and will adjust automatically to orientation changes and adapt to various device screen sizes. This is good but the flip size is that you cannot directly change the positions of your control using their x,y coordinates.
Instead, you can change values in the constraints you defined in the storyboard. for example, if you button has a "Top" distancs constraint with a control above it (or the edge of the view), you can change the constraint's "constant" attribute to indirectly change the position of the control.
In order to manipulate the contraints in your code, you can create IBOutlets for them just as you would do with any control.