changing the accessibility label in control center (remote command center) - ios

I've got a simple app that plays a radiostation. I've added a MPRemoteCommandCenter to let the user control the audio via the control center.
Thats all working fine.
However, I want to change their accessibility labels. But this is the part where things don't work as expected.
I've set up my remoteCommandCenter as follows:
let remoteCommandCenter = MPRemoteCommandCenter.shared()
Then, I added controls and handlers:
remoteCommandCenter.playCommand.isEnabled = true
remoteCommandCenter.playCommand.addTarget(self, action: #selector(ExternalPlaybackController.handleExternalPlayPauseCommandEvent(_:)))
And then, I want to add some accessibility label:
remoteCommandCenter.playCommand.accessibilityLabel = "Play radio"
This is were things don't work. If I debug the code, the compiler will execute that line. What am I making wrong?
Can you even change the accessibility labels of the remoteCommandCenter?

Can you even change the accessibility labels of the remoteCommandCenter?
I never worked with this kind of component but I think that it is ignored by VoiceOver because the screen reader doesn't recognize it as an accessibility element.
In my view, your code compiles with no problems because your accessibility properties belong to the UIAccessibility informal protocol which means that it's well recognized as code.
However, it's not interpreted by VoiceOver as information to be read out because your element isn't a kind of a UIKit control.
I suggest to create an UIAccessibilityElement for your playCommand so as to customize its behaviour as you wish ⟹ Apple doc states :
The UIAccessibility informal protocol is also implemented by the UIAccessibilityElement class, which represents custom user interface objects. If you create a completely custom UIView subclass, you might need to create an instance of UIAccessibilityElement to represent it. In this case, you would support all the UIAccessibility properties to correctly set and return the accessibility element’s properties.

Related

Is it possible to assign an accessibility action to a UILabel?

In our current UI, next to certain labels, we have a help-tip button that when clicked, explains the details of what the label references. As such, VoiceOver identifies these two items as separate accessibility items.
However, when using accessibility, we're hoping we can just do everything in the label itself. This way when the label gets focused, the user will here 'Account value, $20 (the accessibilityLabel), double-tap for help (the accessibilityHint)'
However, unlike a button, a label doesn't have an action associated with it so I'm not sure how to wire up actually triggering the accessibility gesture indicating I want to do something.
Short of converting all of our labels over to buttons, is there any way to listen to the accessibility 'action' method on our labels?
My current work-around is to make only the Help-tip buttons accessible, then move all the relevant information to their accessibility properties, but that seems like code smell as it's easy for a developer to miss that when updating the code.
In your UILabel subclass, override accessibilityActivate() and implement whatever double-tapping should do:
override func accessibilityActivate() -> Bool {
// do things...
return true
}
If the action can fail, return false in those instances.
Have your tried adding a UITapGestureRecognizer to the Labels?
Something like :
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapResponse:")
tapGesture.numberOfTapsRequired = 1
sampleLabel.userInteractionEnabled = true
sampleLabel.addGestureRecognizer(tapGesture)
func tapResponse(recognizer: UITapGestureRecognizer) {
print("tap")
}
Ok, this was easier than I thought. To make a UILabel respond to accessibility actions similar to how a button does, you simply implement a UITapGestureRecognizer. The Accessibility framework uses that just like any other UIView.
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:#selector(labelTapped))
testLabel.userInteractionEnabled = true
testLabel.addGestureRecognizer(tapGestureRecognizer)
Once you do that, your label will respond to accessibility actions.
Group your label and your hint button as one unique accessible element.
Once done, you can use :
The accessibilityActivationPoint property to define the hint button to be triggered when the double tap occurs.
The accessibilityActivate method to indicate the action to be done when you double tap your new element.
According to your environment, I don't recommend to implement a custom action for such a simple use case... the two solutions above should do the job.
Absolutely! You can do this by using UIAccessibilityCustomActions on the accessibility element rather than using tap gesture recognizers. This is because accessibility operates differently than normal users and single tapping while the voice over focus lands somewhere will not give you the desired result as in the case of a normal use, nor will it permit you to execute multiple options on the same accessibility element.
At their recent WWDC, Apple put out an excellent video explaining how to add UIAccessibilityCustomActions to any kind of accessibility element. If you start this video 33 minutes in, you will be able to see how this is implemented.
Once in place, your Voice Over users will be able to scroll through the options and select the one that most suits his/her intentions, thereby permitting multiple actions to be accessible from the same UILabel.

How to get VoiceOver to announce section labels in iOS?

In the iPhone Weather App, when using VoiceOver, I noticed that tapping into a section for the first time, it will announce the section.
For example, in iOS 9, tapping on any item the middle strip for the first time will announce "Hourly forecasts" before continuing to describe the element you tapped on. Tapping anything else in the strip will not announce "hourly forecasts".
Tapping on anything on the bottom table, will announce "Daily forecasts" before continuing to describe the element you tapped on. Tapping anything else in this table will not prefix with "Daily Forecasts".
Is there a simple API to name sections of your app? Or do you have to do this manually by tracking the voiceover cursor and dynamically changing your label? (Does this even work? Can you change the accessibilityLabel after something is tapped but before it is read?)
There are two approaches I guess:
Subclassing the UITableViewCell and overriding the accessibilityLabel.
- (NSString *) accessibilityLabel
{
NSString* voiceOverString;
// append section title on voiceOverString and then the elements value
return voiceOverString;
}
See this link from Apple docs:
You can setAccessibilityLabel of the cell from cellForRowAtIndexPath. The example is for the weather app itself.
Is there a simple API to name sections of your app?
It seems like the most appropriate reference is Apple's Accessibility Programming Guide.
And its API, Apple's UIAccessibility Documentation.
Setting the shouldGroupAccessibilityChildren property seems like the best way to accomplish your goal. The linked API describes it as,
A Boolean value indicating whether VoiceOver should group together the elements that are children of the receiver, regardless of their positions on the screen. Setting the value of this property to YES on the parent view of the items in the vertical columns causes VoiceOver to respect the app’s grouping and navigate them correctly.
Things to keep in mind:
Is the target element an accessibility element? (you can check using the isAccessibilityElement property; standard UIKit controls and views implement the UIAccessibility protocol by default)
If so, you just need to set its accessibility attributes
If not, you need to change its value, view.isAccessibilityElement = true
The accessibilityLabel property identifies the element
The accessibilityHint property describes the action triggered by an element
You can set accessibility attributes in the storyboard
Or, you can set accessibility attributes in the implementation of your view subclass

iOS Accessibility - is there a way to tell when VoiceOver has changed focus?

I'd like to call a method every time a different element is focused while VoiceOver is active. I was hoping there would be some UIAccessibilityNotification for this, but I can't seem to find any.
Ultimately, my goal is to add an additional condition prior to reading the accessibility label. For example, as opposed to saying (by default) "If UIButton becomes focused: read label", I'd like to be able to say "When UIButton becomes focused AND UIButton's background color is blue: read label".
So my question is: how do I either add an additional condition prior to reading the label, or receive a notification when a new element becomes focused?
You can't explicitly tell when the user moves the VoiceOver cursor (just like you can't tell where a sighted user is looking).
For the behavior you want, you have two options:
Set the button's accessibilityLabel to an appropriate value whenever the other conditions change.
Subclass UIButton and override its accessibilityLabel getter method:
- (NSString *) accessibilityLabel {
if (SOME_CONDITION) {
return #"Hooray!";
} else {
return #"Womp womp";
}
}
If you need to disable an item entirely, rather than returning nil or a blank string, you should set its accessibilityElementsHidden property to YES.
You can use the UIAccessibilityFocus protocol to detect changes in focus by accessibility clients (including VoiceOver). Note that UIAccessibilityFocus is an informal protocol that each accessibility element must implement independently.
That said, for your use case, Aaron is right to suggest returning a different accessibilityLabel under each condition.

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.

UIAccessibility - containers

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.

Resources