UIAccessibility - containers - ios

There's a "Containers" rotor option in Voiceover which allows the user to quickly navigate through "high level" sections of the screen via single finger swipe up and swipe down actions. For example, the Calendar app has three high level items: navbar, contents and toolbar.
My app uses custom UIView subclasses and, no matter what I try to do, all my views seem to belong to a single container. I can't split them into logical sections. I tried putting them in separate views implementing the UIAccessibilityContainer protocol and setting a few of the accessibility properties on the parent views.
Does anyone know how to create multiple containers?

I did some digging on this issue and think its a private trait Apple is using. First I noticed the only containers recognized are standard UIKit type objects like UITableViews, UITabBars, UINavigationBars, etc. So next I used the debugger to inspect the value of the accessibility traits for these components. They're all 0x200000000000. Just to be sure I didn't miss an UIAccessibilityTrait I checked all of their values. None of them match the value. Furthermore if you set your views accessibility traits to this mysterious value it'll work just like you want! I tried determining the location of this constant but didn't have much luck. If you want to do more digging it looks like apple stores accessibilityTraits using an NSObject category that uses associated objects with some constant value named AXTraitsIdentifier.
Practically speaking you could do something like the below but since its not defined in a public API its functionality could change in the future
//Note the navBar has to be run through a voice over pass before the value is set :( or you can just directly set the value to 0x200000000000.
myContainerView.accessibilityTraits = navBar.accessibilityTraits;
I'd love to hear if anyone one else has info on this? So far I haven't found an ideal solution.

I have been able to make the views in my app reachable by single finger swipe up and swipe down actions when the "Containers" rotor option is selected by setting the accessibilityContainerType property of my views to semanticGroup.

Related

Correct implementation of VoiceOver accessibility in iOS with `accessibilityLabel` and `accessibilityHint`

I'm trying to optimize my app. What is the best practice method for implementing accessibility in iOS with accessibilityLabel and accessibilityHint? Are accessibility labels always required?
My app is very dynamic and objects in the view get updated frequently depending on the user's action. For example, a view might start off as one shape and later turn into another shape. Accordingly, the accessibilityLabel and accessibilityHint of each object is updated frequently to reflect the changed view for users with VoiceOver turned on.
Questions
Is it safe to expect that when VoiceOver is not running (i.e. !UIAccessibilityIsVoiceOverRunning()) then setting
accessibilityLabel and accessibilityHint is completely
unnecessary?
Are there assistive technologies other than VoiceOver that a user might use that access accessibilityLabel and accessibilityHint?
What is the best practice method for implementing accessibility in iOS with accessibilityLabel and accessibilityHint?
You should consider, rather than "setting" the accessibility label, overriding the property implementation. Allow the view that requires an accessibility label to calculate its value when required, rather than keeping the value in sync constantly. Much easier! You would do so with code that looks like this.
override public var accessibilityLabel: String? {
get {
return "Calculated label"
}
set {
//Specifically do nothing. We're not "setting a property" we're responding to changes of the internal views.
}
}
Are accessibility labels always required?
Yes and no. Accessibility labels are always required for elements that present information and are individually focusable (you may have a group f controls, wrapped in one layout, with one accessibility label). I can say with reasonably certainty that in the context of your question, removing the accessibility label is absolutely the incorrect thing to do.
Is it safe to expect that when VoiceOver is not running (i.e.
!UIAccessibilityIsVoiceOverRunning()) then setting accessibilityLabel
and accessibilityHint is completely unnecessary?
No, there are other uses for accessibility properties outside of VoiceOver.
Are there assistive technologies other than VoiceOver that a user
might use that access accessibilityLabel and accessibilityHint?
There are certainly assistive technologies outside of VoiceOver, and they are dependent on a a myriad of accessibility properties. (braille boards, switch access, etc)
Conclusion
It seems to me like you are trying to get around accessibility as a software practice and rationalize doing so. This is the wrong thought process. HOWEVER, it may indeed be impractical to make the view that you have accessible because of some fundamental design problem. You should consider whether or not the thing you have is designed in a way that it can be made accessible from a development/API point of view. The APIs do limit you from accomplishing certain things. Whether or not your up against one of these limitations is NOT a question you can ask on StackOverflow and the solution is NOT to omit accessibility information. It is to redesign the control OR perhaps to provide an alternate accessible implementation... though this route should be considered VERY carefully. In general separate is not equal.

Accessing nested UIElements in Xcode 7 UI Testing

I am having trouble locating XCUIElements on a screen for the app I am testing. I realize you can access a button for example via something like:
app.buttons[].elementBoundByIndex(0)
But the problem is sometimes, the component is not found. Like in a case where I have a Button in a cell in a UITableView. I try to make an XCUIElementQuery to find the button, and it is not there. I try to look for tables or tableviews or collection views and even though they are in the view controller, they are not found in UI Testing. The count of the returned array will be zero.
I attempted originally to record the test, but clicking the element I am trying to access did not work. Xcode detected it as an "Other Element" and when trying to tap during, playback the application does not advance.
Is there a high level way to access a component like a UIView high in the UI hierarchy to cascade down?
I didn't know this at the time, but what I was looking for was basically debugDescription:
Set a breakpoint when you hit the area you're trying to debug. Then
po print(app.debugDescription)
in the debug console. You will see the hierarchy then.
Ideally you should set an accessibilityIdentifier on your button and search for it via that. The accessibilityIdentifier should be unique for elements on the screen. You can set an accessibilityIdentifier in the Identity Inspector in Interface Builder (command-option-3) or in code directly. Once you have one, the query looks like:
app.buttons["SomeAccessibilityIdentifier"]
Ryan Poolos answer was the best answer for me as it solved my issue with nested UI Elements
We had to solve it by removing a few accessibility identifiers on superviews in the stack. Not ideal but did get it working without changing actual functionality. – Ryan Poolos
So with that in mind, I found the xib file with the element in question, selected the element, selected the tab "Show the Identity Inspector" on the right panel and unchecked the Accessibility checkbox.
I then did a recording of the element which resulted in:
[[[[[XCUIApplication alloc] init].scrollViews.tables childrenMatchingType:XCUIElementTypeOther] elementBoundByIndex:index] childrenMatchingType:XCUIElementTypeButton].element;
Notice how XCUIElementTypeOther is in the query. This was not the case when the accessibility identifiers were enabled as I would get:
[[[[[XCUIApplication alloc] init].scrollViews.tables.otherElements containingType:XCUIElementTypeStaticText identifier:#"username"] childrenMatchingType:XCUIElementTypeButton].element tap];
Obviously I wouldnt know the username as it would always change and this was a major problem. But after removing the accessibility identifiers,
containingType:XCUIElementTypeStaticText identifier:#"username"
changed to
childrenMatchingType:XCUIElementTypeOther] elementBoundByIndex:index
SUCCESS :)
In my own experience you being able to find an element (an UIView in your case as UIButton inherits from UIview) through its accessibilityIdentifier depends on how you added it to the view.
I assume that in your case you added the button to the cell programatically with addSubView. If that's the case, probably you will not be able to access to it. I've had the same problem and asked the question here but no proper solution at the moment, so my advice is try to avoid adding views with addSubView the moment...

Swipe to switch focused accessibility element iOS

When the user has Voice Over on in certain apps, a one handed swipe to the right or the left changes the focused accessibility element and speaks it (for example, the App Store top charts view). I would like to have this in my own app (which uses a storyboard).
I can think of several ways to do this myself with a swipe gesture recognizer and a list of accessibility elements in order, but it seems like there must be a way to do this in the accessibility API. However, my research has turned up nothing.
Is this a built in feature? If so, how can I add it in my storyboard or in code?
Edit:
Per advice from one of the answer I have implemented the UIAccessibility protocol for my view.Here is the code.
- (NSInteger)accessibilityElementCount{
return 4;
}
- (id)accessibilityElementAtIndex:(NSInteger)index{
return [#[self.menuButton, self.firstButton, self.secondButton, self.thirdButton] objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element{
return [#[self.menuButton, self.firstButton, self.secondButton, self.thirdButton] indexOfObject:element];
}
The view I am having this issue with is defined in an interface builder storyboard. As you can no doubt infer from the code, it has 3 buttons as subviews.
What you are describing is the built-in behavior for VoiceOver and can't be changed on a per-app basis.
If you want to modify the order elements are focused, look at the UIAccessibilityContainer protocol for iOS 7 or accessibilityElements property of NSObject for iOS 8. If you don't want to implement either of those, you can also simply set accessibilityElementsHidden to YES for elements you want VoiceOver to ignore.
I have fixed the problem by adding accessibility labels to the buttons in the storyboard. Because voice over already spoke their label correctly, I had not bothered to do so before.

How can I add to the iOS VoiceOver rotor for a custom view?

Recently, I've been working to get my application functioning well with VoiceOver. Generally it's been simple and straightforward, but there are some behaviors from system apps that I'd like to emulate, and I'm having a hard time locating the API to set things up.
In particular, I'm interested in adding a couple of options to the VoiceOver "rotor" and responding to them when the user increases and decreases the value. However, despite the fact that apps like Apple's Maps app add items to the rotor and are able to respond, I can't figure out how to do so for my app.
Has anyone succeeded in doing this? And if so, how?
With iOS 8, you can use the -accessibilityCustomActions method to return an array of UIAccessibilityCustomAction objects, representing the actions you'd like to present "rotor-style".
UPDATE: iOS 10 finally adds ability to add custom rotor items to VoiceOver (not the same thing as the "Actions" rotor item) - just add array of UIAccessibilityCustomRotor objects to accessibilityCustomRotors of the appropriate container view.
OLD ANSWER:
There is currently no API to add your own rotor items. You can only implement how some of the existing rotor items work:
"Adjust value" - here you should return UIAccessibilityTraitAdjustable trait for accessibilityTraits and then implement the accessibilityIncrement/accessibilityDecrement methods
"Headings" - you mark some views as UIAccessibilityTraitHeader, then those should be the view the user moves through when the user rotates to "Headings" and flicks up/down
OLD UPDATE: "Actions" - see UIAccessibilityCustomAction
I guess you should file a radar if you need to add custom items to rotor.

how do i make UIKeyInput make repeated deleteBackwards calls

Currently I am using UIKeyinput but it is only sending a single delteBackward event even when I hold down the delete key for a long time.
How can I make it send me multiple event calls when I hold delete down for a long time?
There is no easy way to have the system keyboard do auto-repeat. These leaves you with two options:
Fake it by using an overlay on the keyboard (see the comment by #pho0)
Implement a custom keyboard, install it as the inputView for your view or view controller and implement a custom protocol that supports auto-repeat.
Solution 1 works well if you only need the delete key to auto-repeat, but if you need all the keys to auto-repeat the overlay code becomes as complex as the custom keyboard option. (The overlay needs a rectangle for each key, so why not just replace the underlaying keyboard).
Solution 2 involves a certain amount of "up-front" work... One way you might do this is define a key cap class (like a physical key) and a keyboard layout class.
I have implemented both solutions in projects I have worked on, but I currently use solution 2 since I can create whatever keyboard I like. In the simple case the use need never know that it is not the system keyboard. For power users they can customize the keyboard as they see fit.
For what it is worth, I found it useful to have the keyboard class be dumb; it just communicates that a key has transitioned to being down or has transitioned to being up. An additional class above that decides what action should be taken.
In some ways, I know this is not the answer you were looking for, but I hope it helps,
IDZ
One thing I've seen people do is put a fake button on top of the keyboard button. When someone is holding down on it, have a timer remove the last letter every time it fires.
Hope this helps.

Resources