I have a view controller with this layout (note that yellow and table views are siblings):
I want the yellow view to act as a container, so I'm doing this in viewDidLoad:
yellowView.isAccessibilityElement = true
view.accessibilityElements = [yellowView!, tableView!]
When in Voice Over, I select "Containers" from the accessibility rotor and expect to be able to swipe up and down to move from the yellow view to the table view and back again:
Yellow (swipe down) → TableView (swipe down) → Tabbar (swipe up) → tableView (swipe up) → Yellow
However, this is not the case - after the tableView gains focus, swiping up does not move focus to the yellow view, it just stops there. Swiping down, moves to the tabbar - it seems that my custom view is ignored as container.
I have experimented with many combinations of adding superviews and setting isAccessibilityElement = false to them, but nothing seems to work.
Does anybody know how to solve this?
it seems that my custom view is ignored as container.
Apparently, only native elements may be recognized as containers for the rotor.🤨
I tried by creating an UIAccessibilityElement inside a view defined as its container with an accessibilityContainerType but no results.🤯
I never use the rotor with the container item but this issue has aroused my curiosity. 🤓
I looked into this problem and found out an interesting answer that highlights the same a11y trait value for all the native containers... those that are analyzed by VoiceOver as such at least.
Override the trait value of your specific containers as follows for instance:
override var accessibilityTraits: UIAccessibilityTraits {
get { return UIAccessibilityTraits(rawValue: 0x200000000000) }
set { }
}
This is a workaround because nothing else seems to be done for custom containers but I'm not a big fan of using raw values that may change or may not be used in a future release.
Anyway, following this rationale, you can now setup accessibility containers so that gestures work properly with the VoiceOver rotor. 👍
Did you set an accessibilityLabel on the yellowView or try changing the accessibilityContainerType = .semantic?
I think VoiceOver is not finding yellowView because the rotor is looking for UIAccessibilityContainer but the default container type is .none for UIView. However, a UITableView has a default container type of .semantic.
Related
I am developing a custom control where I am facing this conundrum.
The control has this hierarchy:
Self (the one I am developing) - UIView derived custom control. I am setting isAccessibilityElement = false
Container - UIView. Again, isAccessibilityElement = false
Children - Again, UIView derived custom controls, whose isAccessibilityElement = false
Every child has labels and buttons that must be accessible.
I have a swipe gesture wired to Self, that should navigate between children.
Everything works great without Voiceover.
When the voiceover is ON, the swiping does not work.
I have tried every trick in the book setting self.accessibilityTraits to .allowsDirectInteraction, .adjustable and so on.
The only time swipe works is when I set self.isAccessibilityElement = true on the control itself. This obviously makes the children inaccessible for speaking as well as focusing and tapping.
I have also tried overriding accessibilityActivate(), but it doesn't enable swipe as it should. accessibilityIncrement() and accessibilityDecrement() also fails, along with .adjustable trait.
This being a common design pattern, it seems to me I am missing something very crucial w.r.t. how isAccessibilityElement works with gestures and children views. I am unable to find it in the docs, and wherever I have tried, it doesn't work as expected.
When using ECSlidingViewController2, How can we prevent the under left menu from being tapped with VoiceOver on and the menu is under the top view? Try the BasicMenu example. When on the Home view, you can tap the options on the menu underneath.
If you dont want the voice over to read it, then make it inaccessible. Set the isAccessibleElement to NO.
This is a master switch for whether the element is accessible or not. UIViews and any custom direct subclasses of it are not accessible by default, whereas UIControls are. Elements which are not marked as accessible will be ignored by VoiceOver, and will be skipped when the user is navigating between accessible elements.
- (BOOL)isAccessibilityElement {
//if this is YES, VoiceOver won't continue to look for accessibility elements in this view's subviews
return NO;
}
Also you can set this to any UIView in your app.
Is it possible to change the order in which the VoiceOver feature for accessibility in iPad reads out the elements, when the 'Two-finger Flick Down' gesture is done?
For the attached image, which contains 3 labels and a button, the VoiceOver reads the elements in the following way,
Label 1 -> Label 2 -> Button -> Label 3
Can the order be changed to,
Label 1 -> Label 2 -> Label 3 -> Button
The quickest way to achieve this for your example is to place the three labels in a transparent UIView subclass to serve as a container for your labels. This subclass will have to be properly setup to let VoiceOver know how to interpret it. If your deployment target is iOS6 then you can simply answer the "should group accessibility children" question in this subclass.
-(BOOL)shouldGroupAccessibilityChildren{
return YES;
}
For below iOS6 it would be more complicated, except that your UIView container subclass would contain only UILabels which are accessibility elements. You could implement it like this:
-(BOOL)isAccessibilityElement{
return NO;
}
-(NSInteger)accessibilityElementCount{
return self.subviews.count;
}
-(id)accessibilityElementAtIndex:(NSInteger)index{
return [self.subviews objectAtIndex:index];
}
-(NSInteger)indexOfAccessibilityElement:(id)element{
return [self.subviews indexOfObject:element];
}
I have tested this example code and it does what you are looking for, if you need any clarification please add a comment. Always happy to help make things more accessible.
I tried setting the shouldGroupAccessibilityChildren to YES but it didn't work for me.
What did work for me was setting the accessibility label of the parent view directly (because I wanted all the items to be read in one go/one VoiceOver gesture).
[cell setAccessibilityLabel:[NSString stringWithFormat:#"%#, %#", cityLabel, temperatureLabel]];
The above snippet of codes is from Apple's documentation Enhancing the Accessibility of Table View Cells
In Swift, attaching an IBOutlet to the parent UIView, then setting shouldGroupAccessibilityChildren to true will suffice.
abc.shouldGroupAccessibilityChildren = true
I did note that if also setting isAccessibilityElement = true the grouping will not take effect. Similarly, checking the accessibility checkbox in the storyboard or xib will also prevent the grouping from taking place.
I think you can do it in the storyboard. The VoiceOver order is determined by the order of the views in the document outline.
Just drag and drop the views in the view hierarchy in the right order.
I’m implementing accessibility in a custom UITableViewCell class. I have a fairly simple overflow menu with a couple of buttons inside it, which are hidden until an ellipsis button is pushed that slides open and closes the overflow.
In my cell's initialiser I’m setting the accessibilityElementsHidden of my overflowContainer to YES. This seems to work, when scrolling through using VoiceOver, those views are skipped.
Then, when opening the cell, in the completion handler of the UIView animation, I set that same accessibilityElementsHidden of the same overflowContainer to NO. This doesn’t seem to have any effect, those elements are still skipped.
I’ve also tried posting UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil) before / after / when changing the accessibilityElementsHidden BOOL, but this also appears to have no effect on the situation.
Basically I’d like to toggle accessibilityElementsHidden on a couple of UIView instances at a specific point. Could anyone let me know what I may be doing wrong?
Here’s the code I fire when the overflow opens:
- (void)cellOverflowDidShow:(MyCell *)cell
{
self.overflowContainer.isAccessibilityElement = YES;
self.firstButton.isAccessibilityElement = YES;
self.secondButton.isAccessibilityElement = YES;
self.thirdButton.isAccessibilityElement = YES;
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.firstButton);
}
I fire the opposite when closing the cell (set all to NO and post notification again). And when initializing the cell, all I set is:
self.overflowContainer.isAccessibilityElement = NO;
Absolutely no idea why it shouldn’t be working, it appears I’m doing everything 100% correctly. If I don’t set the line in the initializer, the buttons all appear accessible (all the time). So it appears that the first call, be it YES or NO, works, but any subsequent ones are ignored.
In the visible state, you declare the overflow container to be an accessibility element. Thus, VoiceOver will allow the user to focus it rather than navigate child elements. Instead of toggling whether it's an accessibility element, keep self.overflowContainer.isAccessibilityElement set to NO and toggle the accessibility of its children, firstButton, secondButton, and thirdButton.
A shorthand for setting the accessibility of child elements is accessibilityElementsHidden. Try setting self.overflowContainer.accessibilityElementsHidden to NO when the view appears and YES when it disappears.
You may still need to trigger a layout change notification, regardless.
Is it possible to change the order in which the VoiceOver feature for accessibility in iPad reads out the elements, when the 'Two-finger Flick Down' gesture is done?
For the attached image, which contains 3 labels and a button, the VoiceOver reads the elements in the following way,
Label 1 -> Label 2 -> Button -> Label 3
Can the order be changed to,
Label 1 -> Label 2 -> Label 3 -> Button
The quickest way to achieve this for your example is to place the three labels in a transparent UIView subclass to serve as a container for your labels. This subclass will have to be properly setup to let VoiceOver know how to interpret it. If your deployment target is iOS6 then you can simply answer the "should group accessibility children" question in this subclass.
-(BOOL)shouldGroupAccessibilityChildren{
return YES;
}
For below iOS6 it would be more complicated, except that your UIView container subclass would contain only UILabels which are accessibility elements. You could implement it like this:
-(BOOL)isAccessibilityElement{
return NO;
}
-(NSInteger)accessibilityElementCount{
return self.subviews.count;
}
-(id)accessibilityElementAtIndex:(NSInteger)index{
return [self.subviews objectAtIndex:index];
}
-(NSInteger)indexOfAccessibilityElement:(id)element{
return [self.subviews indexOfObject:element];
}
I have tested this example code and it does what you are looking for, if you need any clarification please add a comment. Always happy to help make things more accessible.
I tried setting the shouldGroupAccessibilityChildren to YES but it didn't work for me.
What did work for me was setting the accessibility label of the parent view directly (because I wanted all the items to be read in one go/one VoiceOver gesture).
[cell setAccessibilityLabel:[NSString stringWithFormat:#"%#, %#", cityLabel, temperatureLabel]];
The above snippet of codes is from Apple's documentation Enhancing the Accessibility of Table View Cells
In Swift, attaching an IBOutlet to the parent UIView, then setting shouldGroupAccessibilityChildren to true will suffice.
abc.shouldGroupAccessibilityChildren = true
I did note that if also setting isAccessibilityElement = true the grouping will not take effect. Similarly, checking the accessibility checkbox in the storyboard or xib will also prevent the grouping from taking place.
I think you can do it in the storyboard. The VoiceOver order is determined by the order of the views in the document outline.
Just drag and drop the views in the view hierarchy in the right order.