I'm adding Accessibility support to my iOS app and I'm having trouble with a collection view in one of my table view cells.
For example, when the user scrolls (horizontally) from the first cell to the second cell, Accessibility still reads the contents of the first cell. If I try to tap on a view in the second cell, it highlights an empty space to the left of the second cell (where the first cell would be but no longer visible) and reads the contents of the first cell.
When the collection view is not in a table view cell (i.e. a subview of a UIView), this does not happen.
I'm suspecting this has something to do with calling UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification) and I've tried calling it in many different places, but nothing has worked.
The following two screenshots show a collection view inside a UIView. Accessibility is enabled, so it gets selected with a black border when tapped.
When user taps first cell, it will get selected.
When the user taps 'Next', goes to the second cell, and taps the cell, the new cell will get selected.
The next two screenshots show the collection view inside a table view cell.
When the user taps the first cell, it gets selected and VoiceOver properly reads "I'm label 0".
However, when the user taps 'Next', goes to the next cell, and taps the second cell, it does not get selected and VoiceOver will still read, "I'm label 0".
The code is available here on github.
I had simillar problem which I eventually resolved.
I think that you have mismatched elements with
isAccessibilityElement = true
over each other.
I have a table view which scrolls vertically and each cell contains a title and collection view which scrolls horizontally.
I set isAccessibilityElement to true only on title and collection view cells, false on the rest.
Then, I subclassed UICollectionView and overrode the following NSObject methods:
func accessibilityElementCount() -> Int
func accessibilityElement(at: Int) -> Any?
func index(ofAccessibilityElement element: Any) -> Int
It basically just tells the voice over that your collection view has these accessible elements. The collection view itself is not accessible which is not a problem, the contrary. You could probably use
open var accessibilityElements: [Any]?
instead.
Here is some more reading from documentation (UIAccessibility.h):
UIAccessibilityContainer methods can be overridden to vend individual elements that are managed by a single UIView.
For example, a single UIView might draw several items that (to an end user) have separate meaning and functionality. It is important to vend each item as an individual accessibility element.
Sub-elements of a container that are not represented by concrete UIView
instances (perhaps painted text or icons) can be represented using instances of UIAccessibilityElement class (see UIAccessibilityElement.h).
Accessibility containers MUST return NO to -isAccessibilityElement.
To allow nice 3-finger voice over scroll you probably want to override
func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool
as well and scroll your collection view accordingly.
You may want to try this:
let nextCell = collectionView.cellForItemAtIndexPath(nextIndexPath)
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
argument: nextCell)
in your onNextButtonTapped method, after your call to scrollToItemAtIndexPath.
This will focus Accessibility on the next collection view cell.
Related
I have a setup a collection view with remembersLastFocusedIndexPath set to true.
CollectionView is designed such a way that it has nested collection views,
CollectionView
-> Section 1
--->Header
--->Cell nested with collectionview
-> Section 2
--->Header
--->Cell nested with collectionview
....so on....
There is pagination too.
Now if user scrolls to say 2nd item in 7th section and clicks menu in tv remote, I am scrolling collection to top. [either by setting content offset to 0 or scrolling to 0th index]
Requirement: After scrolling to 0th index, if user tries to focus on first cell in 0th section, I want focus to be at 0th index.
Issue: But now collection view remembers last focused index path and scrolls to 2nd item in 7th section.
Expected: I do not want collection view to remember the last focused index path in this case. In all other cases, it should be remembered. i.e, after scrolling to top, I want do something like
collectionView.scrollToTop()
resetFocus() //which should reset collection view's focus to initial index.
func resetFocus() {
//What should be written here to reset the focus of collection view.
}
try also implementing indexPathForPreferredFocusedView after you reset focus. Documentation here.
Presently, I have a container view holding a UICollectionView, which has a number of accessibility-enabled cells.
While using VoiceOver, I can cycle through all the elements in the app, and when I get to my container view, it will cycle through all the cells and their labels (I don't have any accessibility methods or properties enabled on my container view).
However, I'd like it so that I can assign an accessibility label to the UICollectionView so that VO reads the UICollectionView's accessibility label FIRST, before it reads the first cell's accessibility label.
ie,
Button immediately above CollectionView's label: "Button"
CollectionView's label: "Filter Bar"
Cell 1's (default selected) label: "Sports"
Cell 2's label: "Chevrolet"
Scenario:
When the user has Button selected, and swipes to the right, the first cell in the table view gets selected.
Expected Result:
VO reads: "Filter Bar, selected, Sports, button"
Actual Result:
VO reads: "selected, Sports, button".
Is it possible to make VO read the accessibility label of the collection view first?
A UICollectionView may be seen as a container inside which many elements are embedded (its cells) and, as is, you can't have simultaneously a parent view (the collection view) and its child views (its cells) that are both accessible with VoiceOver: either the collection view can be selected or its cells.
Is it possible to make VO read the accessibility label of the collection view first?
Following the explanation above, you can't with the present configuration.
However, a solution may be to create a UIAccessbilityElement that wraps your collection view with the simple label you want to be read out: this element will be selected before the collection view cells are in turn.
Finally, I strongly recommend to take a look at the demo inside this detailed WWDC video that may provide some useful tips for your use case.
⚠️ ⬛️◼️🔳▪️ EDIT ▪️🔳◼️⬛️ ⚠️ (after MarkS's comments)
I'm trying to figure out a way to wrap it, but so far I've been unsuccessful.
Try this code snippet hereafter to create your accessibility element:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let a11yWrapper = UIAccessibilityElement(accessibilityContainer: view!)
a11yWrapper.accessibilityFrameInContainerSpace = CGRect.init(x: 40.0,
y: 40.0,
width: 100.0,
height: 50.0)
a11yWrapper.accessibilityLabel = "Filter Bar"
a11yWrapper.accessibilityTraits = .staticText
view.accessibilityElements = [a11yWrapper]
}
}
Adapt it to your app environment and that should make VO read the accessibility label of the collection view first.
There aren't any examples in the documentation which specifically go over this situation.
I think it's because your design shouldn't be done this way.
Usually, it's either the collection view itself or its cells that are accessible, not both as explained earlier.
However, according to what you exposed in your post, you could use the custom actions to reach your purpose as well: each action will correspond to a particular cell... that's definitely not perfect but that's as close as you can get to what you’re asking.
I have got a TableView and each cell is covered by a Text View.
I think that because of this reason I can't select any cell.
Whenever I try to print it I don't get anything:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var i = Int()
i = indexPath.row
print("i=\(i)")
}
**Its print and not println because of Swift 2.0.
Please ensure following:
You are correctly setting your view controller as delegate of the table view.
Your touches are intercepted by text view and are not reaching underneath cell. To fix this set userInteractionEnabled to false in cellForRowAtIndexPath: and set it back to true in didSelectRowAtIndexPath:.
Take a look at UITextView's userInteractionEnabled, multipleTouchEnabled, and exclusiveTouch. Those properties alter whether or not a view receives touches; if it doesn't, the touches get passed to the view behind it (or at least I think that's how it works). I forget where it is, but there's also a method that's called to ask a view which view should be the target of a specific touch, which would allow you to explicitly tell it to send them to the underlying view.
However, from a design perspective, I would re-evaluate having a UITextView on top of a UITableViewCell - the text view is a scroll view and the cell is in a scroll view, so they will always conflict. UILabel is generally more appropriate for putting in a text view.
For those who are using storyboard just uncheck User Interaction Enabled and Multiple Touch as showing below in the screenshot:
I have listed below points for your solution.
you need to check delegate is connected to your view controller.
next you need to check table view selection is enabled for your table view you can check it from table view attributed inspector.
next make sure user interaction is enabled for your text view.
Hope it will be the right solution for you.
I have a table view with a lot of cells and not every cell is visible on a screen. When I check with
table.cells.staticTexts.matchingIdentifier("My Cell").element.exists
It returns true but the cell is not visible on the screen and I cannot tap on it. Because whenever I tap on it, a test fails.
How to check if an element is visible on a screen? Or how to tap on an element that is not visible?
Use the hittable property instead of exists.
The class reference for XCUIElement explains that the hittable property will only return true if the element can be touched.
table.cells.staticTexts.matchingIdentifier("My Cell").element.hittable
Instead of using element.exists try using element.hittable. This has worked for me, exists returns true if the element is currently in display hierarchy even though it is outside the screen view. hittable returns true only if the element is on screen and its hittable.
The tableview method cellForRowAtIndexPath (NOT the delegate method of the same name) will return the cell at some index path if it is currently being displayed, or nil if it is not displayed.
If the user clicks on a button (or if anything happens with any view in your cell), you can also go up the view hierarchy to first find the cell, then the table view, and the method indexPathForCell will give you the index path of the cell.
you can use swipeUp method to scroll down until specific cell will be visible. You can also check that cell exist or not. something like this.
XCUIElementQuery *tableQuery = app.tables;
if (!tablesQuery.cells.staticText[#"some text"].exist){
[tablesQuery.staticTexts[#"visible cell text"] swipeUp];
}
The layout for one of my View Controllers is such: I have a scroll view embedded inside my VC. Inside my scroll view, I have a table view that consists of 5 cell. The first 3 cells consist of a textfield thats pulls its text from a dictionary and this changes depending on certain situations. These textfields are editable and so tapping on them brings up the keyboard, the issue however is that I would like my view to scroll when I tap on the text field because right now they keyboard hides the the third editable text field. Another issue is that at the moment, clicking outside teh table view doesnt cause the keyboard to be dismissed and so the only way of dismissing the keyboard is tapping on the return key. What I would like to happen is that when I tap on either one of the 3 editable fields, the scroll view ought to scroll up a certain number that I define (this is so that I can define how much to scroll depending on which row is currently selected). One of the issues I'm facing is that I can't directly reference these textfields in my VC since they're all created through one prototype cell. My thinking was that I could create a dictionary with the 3 textfields as keys and then the scrollview y coordinates as values and then use that. However , I wasn't sure how to do this in my situation (with the prototype cells). Would really appreciate if someone could show me some sample code on how to do this.
You can reference your text fields by calling cellForRowAtIndexPath: to get the UITableViewCell, then calling viewWithTag: to get your UITextField. Just assign the text fields a tag number. Also, set the text field's delegate to be your view controller so that you can respond to a user tapping to edit text.