Selector not called on selecting menu item after force touch - ios

I have this code, for responding to a menu item being tapped after a force touch:
class InterfaceController: WKInterfaceController {
override init() {
super.init()
self.addMenuItemWithItemIcon(WKMenuItemIcon.Pause, title: "Pause", action: Selector("test"))
}
func test() {
print("test")
}
}
When I force touch, the menu appears. When I tap the "Pause" button, test() is not called.
Any ideas why this might be?

Solved.
Though its the result of a silly mistake (aren't most problems?), I think this is something a lot of people will run into, so I'll keep it here with my answer.
I enabled force touch on the simulator, so I could show the menu. When I'm tapping again on the button, force touch is still enabled, so I'm force touching, thus dismissing the menu.
Solution: Disable force touch before tapping a menu button.

I wanted to comment with my thanks for this solution but I don't have the rep so I'll begin with, THANKS!
However I can also add an addendum to the solution by pointing out the shortcut keys are really handy here for switching between force press pressures.
⇧⌘1 - for shallow pressure (tap)
⇧⌘2 - for deep pressure (force press)
Make sure the iOS Simulator (watch) is focussed and you'll be good to go.

I'd like to add a small detail to make the SIM procedure more explicit because I actually saw this question and answer before and didn't perform it correctly because it wasn't totally obvious (to me at least).
Press ⇧⌘1 for all regular presses.
Press ⇧⌘2 before you use force touch.
Tricky part: After the menu item appears press ⇧⌘1 again before dismissing the menu item!

Related

Is there a way to catch and cancel the back button event in a NativeScript app on the iOS platform?

I'm working on a NativeScript-Angular app for both Android and iOS but have hit a problem with standard back button navigation. I have resolved the issue for Android, but cannot find a solution for iOS.
The event is causing a problem when going back to a particular page where routing data is expected, resulting in the exception:
"Error: Currently in page back navigation - component should be reattached instead of activated".
My Android solution catches the back button event and cancels it, then calls the router to do the navigation.
ngOnInit() {
if (app.android) {
app.android.on(app.AndroidApplication.activityBackPressedEvent,
(args: any) => this.backEvent(args));
}
}
backEvent(args) {
args.cancel = true;
this.backToRegister(false);
}
backToRegister(accepted: boolean){
this.router.navigate(['/register',
this.registerParametersEntered.password,
this.registerParametersEntered.confirmPassword,
this.registerParametersEntered.code,
this.registerParametersEntered.email,
accepted]);
}
I want to do something similar with iOS, such as: -
if (app.ios) {
this.page.on('navigatingFrom', (data) => {
// TODO cancel the back button event
this.backToRegister(false);
})
}
I can't find a way of doing this for iOS - my research is leading me to the conclusion it is not possible to cancel the iOS back button - for example, see here.
Any ideas or alternative suggestions greatly appreciated!
You can't override the back button for iOS. See this SO question. You basically need to create a custom button, you can mimic the appearance of the back button on iOS and add your own event handler. That's how you'd do it in a native iOS app, and how you do it in NativeScript since the native controls are used via NativeScript.
The actionbar in nativescript can have a custom layout inside or you can just use an action-item and position it on the left for iOS, while also hiding the button on Android if you desire.
Another solution would be, instead of catching the back button event just to throw it away/disable it - to just clear the history after you are switching a page where there no "back" to go to.
this.router.navigate(['level1'], {
clearHistory: true
}

What is the reset(_:) method

I am learning iOS app development, and I came across a line which confused me:
Also add a call from reset(_:) so it works when you reset the app
But I can't find a reset(_:) function anywhere, both in ViewController and AppDelegate. Do I have to create the function, or is it something different?
So let's add the additional infos:
Your quote is from Intro to App Development with Swift, Apple's iBook., chapter 17.6 Polishing the Interface, in Disabling sliders subpart.
You missed the previous part:
#IBAction func reset(_ sender: AnyObject) {​} Open the Connections inspector. You’ll see that the button has been
connected to the Touch Up Inside event. This is the standard event
used ​for most buttons. Your reset button will set the value of each
slider to 1 and the isOn property of each switch to false. Add that
code to the new action method.
So your quote was talking about that method that you should have added previously on ViewController.swift in the chapter 17.5 Reset Button.

Buttons in iOS app don't respond to touches around the edge of the screen

I'm relatively new to iOS development, but I'm having a go at working on some open source code for an old game that used to be pretty popular (Eden World Builder)
I've made quite a lot of progress in cleaning up the codebase, making small changes. But there's an issue I can't seem to fix. Every button in the game will not respond to taps around the edges of the screen. If a button is in the corner of the screen, you will have to tap towards the bottom of the button.
I've tried to move the buttons away from the edges, and they work, but that isn't practical for use. So there's something preventing the edges of the screen from registering button taps for some reason, and it doesn't seem to be anything to do with the button target areas themselves.
One thing I've noticed: This game is currently on the App Store, even though it hasn't been updated since 2015. In the App Store version (Which is built from the same code that I have) the issue doesn't occur. It must be something to do with building it in a newer version of Xcode, right?
Any assistance would be very helpful, this has been frustrating me for weeks now. Thanks
The answer to your first question is most likely either that your button is outside its parent view, or a gesture recognizer is interfering.
If a button extends beyond its parent views boundaries (or any of its higher parents' boundaries), it will still be visible as long as the parent doesn't have clipping enabled. The result is that you will still see the button, but it will only respond when touching the parts that are inside the parent view. You can find this visually by using Xcode View UI Hierarchy found in the Debug Navigator.
If it is gesture recognizers that interfere with your button, there are several solutions that might work. Several are described in the link you got from #Anbu in the comment.
The answer to your second question is that old apps are linked against old frameworks. Even if they run on the latest iOS version, they still pull in older versions of the framework, causing them to (mostly) continue work as before. This is done to keep compatibility with legacy code.
Try adding this to viewDidAppear
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let window = view.window,
let recognizers = window.gestureRecognizers {
recognizers.forEach { r in
r.delaysTouchesBegan = false
r.cancelsTouchesInView = false
r.isEnabled = false
}
}
}

UI Testing Failure - Neither element nor any descendant has keyboard focus on secureTextField

This is my case:
let passwordSecureTextField = app.secureTextFields["password"]
passwordSecureTextField.tap()
passwordSecureTextField.typeText("wrong_password") //here is an error
UI Testing Failure - Neither element nor any descendant has keyboard focus. Element:
What is wrong? This is working nice for normal textFields, but problem arise only with secureTextFields. Any workarounds?
This issue caused me a world of pain, but I've managed to figure out a proper solution. In the Simulator, make sure I/O -> Keyboard -> Connect hardware keyboard is off.
Recently we found a hack to make solution from accepted answer persistent. To disable Simulator setting: I/O -> Keyboard -> Connect hardware keyboard from command line one should write:
defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0
It will not affect a simulator which is running - you need to restart simulator or start a new one to make that setting have its effect.
I have written a small extension (Swift) which works perfect for me.
Here is the code:
extension XCTestCase {
func tapElementAndWaitForKeyboardToAppear(element: XCUIElement) {
let keyboard = XCUIApplication().keyboards.element
while (true) {
element.tap()
if keyboard.exists {
break;
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.5))
}
}
}
The main idea is to keep tapping an element (text field) before the keyboard is presented.
Stanislav has the right idea.
In a team environment, you need something that will automatically work. I've come up with a fix here on my blog.
Basically you just paste:
UIPasteboard.generalPasteboard().string = "Their password"
let passwordSecureTextField = app.secureTextFields["password"]
passwordSecureTextField.pressForDuration(1.1)
app.menuItems["Paste"].tap()
Another cause of this error is if there is a parent view of the text field in which you are trying to enter text that is set as an accessibility element (view.isAccessibilityElement = true). In this case, XCTest is not able to get a handle on the subview to enter the text and returns the error.
UI Testing Failure - Neither element nor any descendant has keyboard
focus.
It isn't that no element has focus (as you can often see the keyboard up and blinking cursor in the UITextField), it is just that no element it can reach has focus. I ran into this when attempting to enter text in a UISearchBar. The search bar itself is not the text field, when setting it as an accessibility element, access to the underlying UITextField was blocked. To resolve this, searchBar.accessibilityIdentifier = "My Identifier" was set on the UISearchBar however the isAccessibilityElement was not set to true. After this, test code of the form:
app.otherElements["My Identifier"].tap()
app.otherElements["My Identifier"].typeText("sample text")
Works
It occurred with me for many times.
You have to disable Keyboard Hardware and Same Layout as OSX in your Simulator
Hardware/Keyboard (disable all)
After that keyboard software won't dismiss and your tests can type text
In my case this Hardware -> Keyboard -> Connect Hardware Keyboard -> Disable didn't worked for me.
But when I followed
1) Hardware -> Keyboard -> Connect Hardware Keyboard -> Enabled and run the app
2) Hardware -> Keyboard -> Connect Hardware Keyboard -> Disable.
It worked for me
func pasteTextFieldText(app:XCUIApplication, element:XCUIElement, value:String, clearText:Bool) {
// Get the password into the pasteboard buffer
UIPasteboard.generalPasteboard().string = value
// Bring up the popup menu on the password field
element.tap()
if clearText {
element.buttons["Clear text"].tap()
}
element.doubleTap()
// Tap the Paste button to input the password
app.menuItems["Paste"].tap()
}
Use a sleep between launching the app and typing in data in textfields like this:
sleep(2)
In my case I was keeping getting this error every time and only this solution helped my out.
This maybe help: I just add a "tap" action before the error; that´s all :)
[app.textFields[#"theTitle"] tap];
[app.textFields[#"theTitle"] typeText:#"kk"];
Record case however you want, keyboard or without keyboard attached.
But do the following before playing test.
This following option (connect hardware keyboard) should be unchecked while playing test.
[Reposting Bartłomiej Semańczyk's comment as an answer because it solved the problem for me]
I needed to do Simulator > Reset Contents and Settings in the simulator menu bar to make this start working for me.
Works for me:
extension XCUIElement {
func typeTextAlt(_ text: String) {
// Solution for `Neither element nor any descendant has keyboard focus.`
if !(self.value(forKey: "hasKeyboardFocus") as? Bool ?? false) {
XCUIDevice.shared.press(XCUIDevice.Button.home)
XCUIApplication().activate()
}
self.typeText(text)
}
}
I ran into this issue and was able to fix it in my scenario by taking the solution posted by #AlexDenisov and adding it to my pre-actions for run & test.
The above answers solved it for me when running UI tests while the simulator UI is in the foreground (when you could see the changes in the simulator window). However, it did not work when running them in the background (which is how fastlane ran them, for example).
I had to go to Simulator > Device > Erase All Content and Settings...
To be clear, I manually disabled-enabled-disabled the Hardware Keyboard in the Simulator Settings, set defaults write com.apple.iphonesimulator ConnectHardwareKeyboard 0, and then erased content and settings.
Sometime text fields are not implemented as text fields, or they're wrapped into another UI element and not easily accessible.
Here's a work around:
//XCUIApplication().scrollViews.otherElements.staticTexts["Email"] the locator for the element
RegistrationScreenStep1of2.emailTextField.tap()
let keys = app.keys
keys["p"].tap() //type the keys that you need
//If you stored your data somewhere and need to access that string //you can cats your string to an array and then pass the index //number to key[]
let newUserEmail = Array(newPatient.email())
let password = Array(newPatient.password)
//When you cast your string to an array the elements of the array //are Character so you would need to cast them into string otherwise //Xcode will compain.
let keys = app.keys
keys[String(newUserEmail[0])].tap()
keys[String(newUserEmail[1])].tap()
keys[String(newUserEmail[2])].tap()
keys[String(newUserEmail[3])].tap()
keys[String(newUserEmail[4])].tap()
keys[String(newUserEmail[5])].tap()
The following is from a Swift 5.4, SwiftUI app scenario which would fail UI Testing.
app.cells.firstMatch.tap()
// transition from list view to detail view for item
app.textFields
.matching(identifier: "balance_textedit")
.element.tap()
app.textFields
.matching(identifier: "balance_textedit")
.element.typeText("7620") // :FAIL:
Test: Failed to synthesize event: Neither element nor any descendant has keyboard focus.
Solution: Tap Again
A second tap() was found to pass.
app.cells.firstMatch.tap()
// transition from list view to detail view for item
app.textFields
.matching(identifier: "balance_textedit")
.element.tap()
app.textFields
.matching(identifier: "balance_textedit")
.element.tap()
app.textFields
.matching(identifier: "balance_textedit")
.element.typeText("7620") // :PASS:
However, .doubleTap() and tap(withNumberOfTaps: 2, numberOfTouches: 1) would still fail.
Solution: Await TextField Arrival, Then Tap
Sometimes a view transition needs to expressly await the TextEdit arrival. The approach below awaits the arrival of the targeted TextEdit field.
XCTestCase.swift extension:
extension XCTestCase {
func awaitArrival(element: XCUIElement, timeout: TimeInterval) {
let predicate = NSPredicate(format: "exists == 1")
expectation(for: predicate, evaluatedWith: element)
waitForExpectations(timeout: timeout)
}
func awaitDeparture(element: XCUIElement, timeout: TimeInterval) {
let predicate = NSPredicate(format: "exists == 0")
expectation(for: predicate, evaluatedWith: element)
waitForExpectations(timeout: timeout)
}
}
Example use:
app.cells.firstMatch.tap()
// transition from list view to detail view for item
awaitArrival(
element: app.textFields.matching(identifier: "balance_textedit").element,
timeout: 5)
app.textFields
.matching(identifier: "balance_textedit")
.element.tap()
app.textFields
.matching(identifier: "balance_textedit")
.element.typeText("7620") // :PASS:
Solution: waitForExistence, Then Tap
Use the waitForExistence(timeout:) method provided by XCUIElement.
app.cells.firstMatch.tap()
// transition from list view to detail view for item
app.textFields.matching(identifier: "balance_textedit")
.element.waitForExistence(timeout: 5)
app.textFields
.matching(identifier: "balance_textedit")
.element.tap()
app.textFields
.matching(identifier: "balance_textedit")
.element.typeText("7620") // :PASS:
Your first line is just a query definition, which doesn't mean that passwordSecureTextField would actually exist.
Your second line will dynamically execute the query and try to (re)bind the query to UI element. You should put a breakpoint on it and verify that one and only one element is found. Or just use an assert:
XCTAssertFalse(passwordSecureTextField.exists);
Otherwise it looks ok, tap should force keyboard visible and then typeText should just work. Error log should tell you more info.
Don't messed up, There problem caught the reason is you are recorded your testing time your app will connection hardware keyboard while your automatic testing time simulator takes only software keyboard. so for how to fix this issues. Just use software keyboard on your recording time. you can see the magic.
We encountered the same error when setting the accessibilityIdentifier value for a custom view (UIStackView subclass) containing UIControl subviews. In that case XCTest was unable to get the keyboard focus for the descendent elements.
Our solution was simply to remove the accessibilityIdentifier from our parent view and set the accessibilityIdentifier for the subviews through dedicated properties.
Another answer, but for us the problem was the view was too close to another view that a Gesture recognizer on it. We found we needed the view to be at least 20 pixels away (in our case below). Literally 15 didn't work and 20 or more did. This is strange I'll admit, but we had some UITextViews that were working and some that were not and all were under the same parent and identical other positioning (and variable names of course). The keyboard on or off or whatever made no difference. Accessibility showed the fields. We restarted our computers. We did clean builds. Fresh source checkouts.
No Need to turn on/off keyboard in I/O.
Don't use .typeText for secureTextField, just use
app.keys["p"].tap()
app.keys["a"].tap()
app.keys["s"].tap()
app.keys["s"].tap()
Bonus: You get keyboard sound click :)
Finally, I wrote a script which edits the Simulator's .plist file and sets the ConnectHardwareKeyboard property to false for the selected simulator. You heard it right, it changes the property for the specifically selected simulator inside "DevicePreferences" dictionary rather than editing the global property.
First, create a shell script named disable-hardware-keyboard.sh with the following contents. You can place it within "YourProject/xyzUITests/Scripts/".:
echo "Script: Set ConnectHardwareKeyboard to false for given Simulator UDID"
if [[ $1 != *-*-*-*-* ]]; then
echo "Pass device udid as first argument."
exit 1
else
DEVICE_ID=$1
fi
DEVICE_PREFERENCES_VALUE='<dict><key>ConnectHardwareKeyboard</key><false/></dict>'
killall Simulator # kill restart the simulator to make the plist changes picked up
defaults write com.apple.iphonesimulator DevicePreferences -dict-add $DEVICE_ID $DEVICE_PREFERENCES_VALUE
open -a Simulator # IMPORTANT
Now follow these steps to call it with passing the selected simulator's udid as an argument:
Edit your Xcode scheme (or UI tests specific scheme if you have one)
Go to: Test > Pre-actions
Add new script by tapping "+" symbol > "New Run Script Action".
Important: Inside "Provide build settings from" dropdown choose your main app target, not the UI tests target.
Now add the following script in the text area below.
Script inside Test>Pre-actions:
#!/bin/sh
# $PROJECT_DIR is path to your source project. This is provided when we select "Provide build settings from" to "AppTarget"
# $TARGET_DEVICE_IDENTIFIER is the UDID of the selected simulator
sh $PROJECT_DIR/xyzUITests/Scripts/disable-hardware-keyboard.sh $TARGET_DEVICE_IDENTIFIER
# In order to see output of above script, append following with it:
# | tee ~/Desktop/ui-test-scheme-prescript.txt
Time to test it:
Launch simulator
Enable hardware keyboard for it
Run any UI test with keyboard interaction. Observe the simulator restarts and the hardware keyboard is disabled. And the test's keyboard interaction is working fine. :)
Long story short: hardware keyboard input in iOS Simulator seems to broken for a long time.
The thing is that -[UIApplicationSupport _accessibilityHardwareKeyboardActive] private method from com.apple.UIKit.axbundle sometimes returns NO even though it should return YES (when a text input view is firstResponder, and the cursor is blinking, which literally means that the hardware keyboard is active).
Put this code anywhere in the app to make iOS Simulator believe that if Software Keyboard is inactive then the hardware one is active.
#if TARGET_OS_SIMULATOR
__attribute__((constructor))
static void Fix_UIApplicationAccessibility__accessibilityHardwareKeyboardActive(void) {
{
static id observer = nil;
observer = [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
NSBundle *bundle = note.object;
if ([bundle.bundleIdentifier isEqualToString:#"com.apple.UIKit.axbundle"]) {
Class cls = objc_lookUpClass("UIApplicationAccessibility");
SEL sel = sel_getUid("_accessibilityHardwareKeyboardActive");
Method m = class_getInstanceMethod(cls, sel);
method_setImplementation(m, imp_implementationWithBlock(^BOOL(UIApplication *app) {
return ![[app valueForKey:#"_accessibilitySoftwareKeyboardActive"] boolValue];
}));
}
}];
}
}
#endif
emailTextField.tap() // add this line before typeText
emailTextField.typeText("your text")
The problem for me was the same as for Ted. Actually if password field gets tapped after login field and hardware KB is on, software keyboard will dismiss itself on second field tap, and it's not specific to UI tests.
After some time messing around with AppleScript, here's what I came up with(improvements are welcome):
tell application "Simulator"
activate
tell application "System Events"
try
tell process "Simulator"
tell menu bar 1
tell menu bar item "Hardware"
tell menu "Hardware"
tell menu item "Keyboard"
tell menu "Keyboard"
set menuItem to menu item "Connect Hardware Keyboard"
tell menu item "Connect Hardware Keyboard"
set checkboxStatus to value of attribute "AXMenuItemMarkChar" of menuItem
if checkboxStatus is equal to "✓" then
click
end if
end tell
end tell
end tell
end tell
end tell
end tell
end tell
on error
tell application "System Preferences"
activate
set securityPane to pane id "com.apple.preference.security"
tell securityPane to reveal anchor "Privacy_Accessibility"
display dialog "Xcode needs Universal access to disable hardware keyboard during tests(otherwise tests may fail because of focus issues)"
end tell
end try
end tell
end tell
Create a script file with code above and add it to necessary targets(probably UI tests target only, you may want to add similar script to your development targets to re-enable HW keyboard during development).
You should add Run Script phase in build phases and use it like this:
osascript Path/To/Script/script_name.applescript
Had the same issue with Securetextfields. The connect hardware option in my Simulator was of, but still ran into the issue. Finally, this worked for me (Swift 3):
let enterPasswordSecureTextField = app.secureTextFields["Enter Password"]
enterPasswordSecureTextField.tap()
enterPasswordSecureTextField.typeText("12345678")
What fixed this problem for me was adding a 1 second sleep:
let textField = app.textFields["identifier"]
textField.tap()
sleep(1)
textField.typeText(text)

UITextField can send its action twice. Is this an iOS bug, or did I make an error?

In iOS, I have a UITextField in a UITableViewController scene that is governed by a Navigation Controller. All static cells. The text field has the "Clear on editing" flag switched on. I want to get and save the value entered in the field, so I have the -editingDidEnd: action call my saveData: method in the custom UITableViewController subclass. If I click out of the text field, all is well. However, if I back out of the scene using the Back navigation button, then saveData: gets called twice. Once with the entered value, and a second time with an empty value. This appears to be incorrect behavior.
I've made a demo project and posted it to GitHub here: https://github.com/davehirsch/BugDemo
Am I doing something wrong? Is this an iOS bug? It seems unlikely that it's a bug nobody else has found (and I've looked around for others reporting similar things). I'm running the latest non-beta versions of Xcode and OS X, and the bug is happening in the iOS Simulator (but I'm not sure if it would happen on a real device, I guess).
Yes you are right. It looks like a bug in UINavigationController.
Before and after animation the method [UITextField resignFirstResponder] is called before and after animation. The first call is done by [UINavigationController popViewControllerAnimated:], and the second one in [UINavigationConteoller navigationTransitionView:didEndTransition:fromView:toView:] that call [UIView(Hierarchy) _removeFirstResponderFromSubtree].
Looks like your text field hasn't resigned yet as a first responder before animation ends.
[EDIT]
I recommend to look at rdelmar comment.

Resources