How to use tap gesture in Accessibility in swift? [duplicate] - ios

I'm working on app for blind people first i needed to use swipe gesture and after search i can do it by using
override func accessibilityScroll( direction:
UIAccessibilityScrollDirection)-> Bool {
if direction == UIAccessibilityScrollDirection.Left
{
Do what you want to do
}
}
Now I need to use tap gesture and I can't find any function to use make custom tap in Accessibility can anyone help me pleasee!
Thanks

You shouldn't use tap gesture for UIAccessibility ot try to add tap gestures to UIAccessibility.UIAccessibility is meant for the UIElements where the people can see/hear what is on the screen. You cannot add UIAccessiility for tap gesture functions. What you can do is to post a accessibility notification to the user saying tap to perform something. Again, you need to use buttons to perform an action in UIAccessibility.

You can override accessibilityScroll method:
override func accessibilityScroll(_ direction:
UIAccessibilityScrollDirection) -> Bool {
super.accessibilityScroll(direction)
if direction == UIAccessibilityScrollDirection.left {
// do your stuff
}
if direction == UIAccessibilityScrollDirection.right {
// do your stuff
}
return true
}

Related

Disable DoubleTapGesture Zoom out on MKMapView

I have observed MKMapView zoom-out on Double tap gesture, I didn't find any way to disable it. I tried to add own Double Tap Gesture to catch Double tap action but it still zoom out. Any thought?
There is no API to disable or change double-tap behavior with MKMapView.
But, if you really want to do so, one approach would be to find and remove the double-tap gesture recognizer from the MKMapView object.
In the project you shared, you could do that in makeUIView in your UIMapView class:
func makeUIView(context: UIViewRepresentableContext<UIMapView>) -> UIViewType {
self.configureView(mapView, context: context)
setRegion(to: palce)
if let v = mapView.subviews.first,
let ga1 = v.gestureRecognizers
{
let ga2: [UITapGestureRecognizer] = ga1.compactMap { $0 as? UITapGestureRecognizer } .filter { ($0.numberOfTapsRequired == 2) }
for g in ga2 {
v.removeGestureRecognizer(g)
}
}
return mapView
}
I wouldn't necessarily suggest that you do so, however.
Apple may change the MKMapView object in the future, which could then break this.
User's tend to prefer that common UI elements behave in expected ways.
Personally, I get rather annoyed when using an app and the developer has changed standard functionality of UI elements. For example, if I see a table view row with a disclosure indicator (the right-arrow / chevron), I expect that tapping the row will "push" to another screen related to that row. I've seen apps that do not follow that pattern, and it just gets confusing.

How to know when focused element is refocused in voice over (iOS)

I want to do something when the focused voice-over element is being tapped again.
Function accessibilityElementDidBecomeFocused() will only get called when the element is focused for first time.
When we are again single tapping on the same focused element then this function won't get called. Can anybody suggest some solution?
Can anybody suggest some solution?
Here are some ideas on the fly to detect a single-tap on the same focused element:
Create a variable nbSelections that will count the number of single-taps.
Create a tap gesture recognizer on your element to increment the number of taps for instance:
let tap = UITapGestureRecognizer(target: self,
action: #selector(addTapCounter(info:)))
tap.numberOfTapsRequired = 1
self.addGestureRecognizer(tap)
Add the trait that will allow to catch the simple tap directly on the element:
override var accessibilityTraits: UIAccessibilityTraits {
get { return .allowsDirectInteraction }
set { }
}
Set nbSelections = 0 when the element loses the focus:
override open func accessibilityElementDidLoseFocus() { nbSelections = 0 }
Combining these ideas with the UIAccessibilityFocus informal protocol could be a good line of research to reach your goal.
However, this technical solution assumes that the single-tap is made directly on the element itself (-trait specific) and not anywhere else (I don't see how to catch this event the top off my head).

UITapGestureRecognizer on a text field not as expected

In my class I have 11 UITapGestureRecognizers in an array textViewRecognizer attached to 11 out of 100 UITextFields in an array boxArray. When a Textfield is tapped containing a UIGestureRecognizer it runs tappedTextView where I try to get the index of the first responder.
However, due to some weird ordering in how things are executed, the action function only gives me the first responder of the previous first responder to the one that was just tapped.
Also, I have to double tap to even select the text field I was going for! I need to use the tap function and not the text delegates so this has been a real headache.
I have...
#objc func tappedTextField(_ sender: UITapGestureRecognizer) {
for i in 0...99 {
if (boxArray[i]?.isFirstResponder)! {
if let index = boxArray.index(of: boxArray[i]) {
print(index)
break
}
}
}
}
in my viewDidLoad I have
for i in 0...10 {
textFieldTapRecognizer[i].addTarget(self, action: #selector(self.tappedTextField(_:)))
}
In my class I have
I want to set 11 out of 100 textFields to have this a tap recognizer depending on some conditions (I'm just going to use a regular for loop here)
for i in 0...10 {
boxArray[i]?.addGestureRecognizer(textFieldTapRecognizer[i])
}
Is there anyway I can get it to give me the actual first responder, after the tap was made?
Is there anyway to go around the double tap to select the text field that has a UITapGesture?
Any help is greatly appreciated.
Edited: properly named functions
It sounds like you want to remove the automatic editing behavior on a UITextView. You can grab more control over that with the textViewShouldBeginEditing(_ textView: UITextView) -> Bool UITextViewDelegate method, documented here.
If you return false for that method, this should avoid needing a double tap to get to your gesture recognizer. Depending on your use case, you can then "allow" the tap to go to the text view by returning true for the textView you want to be actually edited.
While I'm not 100% clear on the first responder part of your question, since the textView won't be grabbing first responder if it's not starting it's editing mode, this should address that concern I believe. Good luck!
I would add a Tag to my UITextView and set the UITextViewDelegate to my ViewController.
Then I would add the following Delegate method:
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
print("Textview tag: ", textView.tag)
return false
}

How to hide keyboard in Swift app during UI testing

I just started with UI testing in Xcode 7 and hit this problem:
I need to enter text into a textfield and then click a button. Unfortunately this button is hidden behind the keyboard which appeared while entering text into the textfield. Xcode is trying to scroll to make it visible but my view isn't scrollable so it fails.
My current solution is this:
let textField = app.textFields["placeholder"]
textField.tap()
textField.typeText("my text")
app.childrenMatchingType(.Window).elementBoundByIndex(0).tap() // hide keyboard
app.buttons["hidden button"].tap()
I can do this because my ViewController is intercepting touches:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
view.endEditing(false)
super.touchesBegan(touches, withEvent: event)
}
I am not really happy about my solution, is there any other way how to hide the keyboard during UI testing?
If you have set up your text fields to resign FirstResponder (either via textField.resignFirstResponder() or self.view.endEditing(true)) in the textFieldShouldReturn() delegate method, then
textField.typeText("\n")
will do it.
Swift 5 helper function
func dismissKeyboardIfPresent() {
if app.keyboards.element(boundBy: 0).exists {
if UIDevice.current.userInterfaceIdiom == .pad {
app.keyboards.buttons["Hide keyboard"].tap()
} else {
app.toolbars.buttons["Done"].tap()
}
}
}
Based on a question to Joe's blog, I have an issue in which after a few runs on simulator the keyboards fails to hide using this piece of code:
XCUIApplication().keyboard.buttons["Hide keyboard"]
So, I changed it to: (thanks Joe)
XCUIApplication().keyboard.buttons["Hide keyboard"]
let firstKey = XCUIApplication().keys.elementBoundByIndex(0)
if firstKey.exists {
app.typeText("\n")
}
What I try to do here is detecting if the keyboard stills open after tap the hide button, if it is up, I type a "\n", which in my case closes the keyboard too.
This also happens to be tricky, because sometimes the simulator lost the focus of the keyboard typing and this might make the test fail, but in my experience the failure rate is lower than the other approaches I've taken.
I hope this can help.
I always use this to programmatically hide the keyboard in Swift UITesting:
XCUIApplication().keyboards.buttons["Hide keyboard"].tap()
XCUIApplication().toolbars.buttons["Done"].tap()
With Swift 4.2, you can accomplish this now with the following snippet:
let app = XCUIApplication()
if app.keys.element(boundBy: 0).exists {
app.typeText("\n")
}
The answer to your question lies not in your test code but in your app code. If a user cannot enter text using the on-screen software keyboard and then tap on the button, you should either make the test dismiss the keyboard (as a user would have to, in order to tap on the button) or make the view scrollable.
Just make sure that the keyboard is turned off in the simulator before running the tests.
Hardware->Keyboard->Connect Hardware Keyboard.
Then enter your text using the paste board
textField.tap()
UIPasteboard.generalPasteboard().string = "Some text"
textField.doubleTap()
app.menuItems["paste"].tap()
I prefer to search for multiple elements that are possibly visible to tap, or continue, or whatever you want to call it. And choose the right one.
class ElementTapHelper {
///Possible elements to search for.
var elements:[XCUIElement] = []
///Possible keyboard element.
var keyboardElement:XCUIElement?
init(elements:[XCUIElement], keyboardElement:XCUIElement? = nil) {
self.elements = elements
self.keyboardElement = keyboardElement
}
func tap() {
let keyboard = XCUIApplication().keyboards.firstMatch
if let key = keyboardElement, keyboard.exists {
let frame = keyboard.frame
if frame != CGRect.zero {
key.forceTap()
return
}
}
for el in elements {
if el.exists && el.isHittable {
el.forceTap()
return
}
}
}
}
extension XCUIElement {
///If the element isn't hittable, try and use coordinate instead.
func forceTap() {
if self.isHittable {
self.tap()
return
}
//if element isn't reporting hittable, grab it's coordinate and tap it.
coordinate(withNormalizedOffset: CGVector(dx:0, dy:0)).tap()
}
}
It works well for me. This is how I would usually use it:
let next1 = XCUIApplication().buttons["Next"]
let keyboardNext = XCUIApplication().keyboards.firstMatch.buttons["Next"]
ElementTapHelper(elements: [next1], keyboardElement: keyboardNext).tap()
Nice thing about this is you can provide multiple elements that could be tapped, and it searches for keyboard element first.
Another benefit of this is if you are testing on real devices the keyboard opens by default. So why not just press the keyboard button?
I only use this helper when there are multiple buttons that do the same thing, and some may be hidden etc.
If you are using IQKeyboardManager you can easily do this:
app.toolbars.buttons["Done"].tap()
This way you capture the "Done" button in the keyboard toolbar and hide the keyboard. It also works for different localizations.

Using iOS pan gesture to highlight buttons in grid

So far I have a grid of buttons and have attached a pan gesture recognizer to the view. I can track the events and get the location of the finger as it moves but there doesn't seem to be the equivalent of a "mouseEnter" message to use to get info about or control properties (such as the highlighting) of the other buttons I pass over.
Am I missing something? How can I accomplish, say, highlighting the buttons under the users' fingers as they pan over them? Does cocoa touch support this or must something else be done?
Thanks.
You are right, there is no such event. Also UIButton events won't help you either, because those require to actually start gesture inside. What you can do instead is to get location of the point you are currently dragging:
func panDetected(sender : MoreInformativeGestureRecognizer) {
let touchPoint = sender.locationInView(self.view)
}
And now, when you have the point, you can iterate on all the buttons you have and check if the point is inside the button:
let buttons = [UIButton]
let lastActiveButton = UIButton?
...
// Iterate through all the buttons
for button in buttons {
// Check area of the button against your touch
if CGRectContainsPoint(button.frame, touchPoint) {
// You are inside the touch area of the button
// Here, you can for example perform some action if you want, store information
// about the button so you don't do it multiple times etc.. your call :)
self.lastActiveButton = button
}
}
This way you can detect then you go in and out and do whatever you want with events. Hope it helps!
UIButton inherits from UIControl which has a number of events
Have you tried that? You can add a listener to those events, either through nib/storyboard or through code (look under discussion to see how to do this)

Resources