4

I have a UITableViewCell which contains a UICollectionViewCell in each row. This collection view has supplementary views (a header) which appears at the top of the cell.

When navigating with VoiceOver enabled the cells are read in the correct order (header, collectionviewcell1, collectionviewcell2, ...) however, after the view has scrolled (caused by swiping left to right through the cells) the order becomes broken, reading out the UICollectionView cells and then the header.

What could cause this?

I have tried using the UIAccessibilityContainer protocol on the containing UITableViewCell and returning both the number of items in the UICollectionView, plus header, and for the indices returning the header at index 0 and the UICollectionViewCell at the given index. This always highlights the header first, but doesn't navigate through the UICollectionView cells.

I have also tried returning the count as 2 elements (header and UICollectionView) and returning those objects for accessibilityElementAtIndex. This does start with the header, but only reads out the first item in the CollectionView

daentech
  • 1,125
  • 12
  • 16

3 Answers3

2

It turns out the incorrect ordering was caused by using a UITableViewCell as the header view for the UICollectionView. A crash manifested when scrolling with VoiceOver turned on and I managed to stop this crash by returning the contentView of the cell in the UIAccessibilityContainer protocol.

#pragma mark - UIAccessibilityContainer

-(NSInteger)accessibilityElementCount {

    int headerCount = self.headerView ? 1 : 0;
    int footerCount = self.footerView ? 1 : 0;

    return ([self.dataValues count] + headerCount + footerCount;
}

-(id)accessibilityElementAtIndex:(NSInteger)index {
    if (index == 0) {
        if (self.headerView) {
            if ([self.headerView isKindOfClass:[UITableViewCell class]]) {
                UIView *header = ((UITableViewCell *)self.headerView).contentView;
                header.shouldGroupAccessibilityChildren = YES;
                return header;
            }
            return self.headerView;
        }
    }
    if (self.headerView) index -= 1;

    if (index >= [self.dataValues count]) {
        if ([self.footerView isKindOfClass:[UITableViewCell class]]) {
            return ((UITableViewCell *)self.footerView).contentView;
        }
        return self.footerView;
    }

    return [self collectionView:_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
}

The answer to this question pointed me in the right direction:

iOS VoiceOver crash (message sent to deallocated instance)

Community
  • 1
  • 1
daentech
  • 1,125
  • 12
  • 16
2

Swift version solution tested on iOS 11.

override func accessibilityElementCount() -> Int {
    return cellViewModels.count
}

override func accessibilityElement(at index: Int) -> Any? {
    return collectionView.cellForItem(at: IndexPath(item: index, section: 0))
}

Attention: Use collectionView.cellForItem in func accessibilityElement(at index: Int) -> Any? to get the current collection view cell instead of func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell delegate. This is the difference with daentech's solution.

shippo7
  • 407
  • 5
  • 15
0
 override func accessibilityElementCount() -> Int {
        return data.count
    }

    override func accessibilityElement(at index: Int) -> Any? {
        return collectionView.cellForItem(at: IndexPath(item: index, section: 0))
    }
Rushikesh Welkar
  • 143
  • 1
  • 2
  • 8
  • 1
    Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, **can you [edit] your answer to include an explanation of what you're doing** and why you believe it is the best approach? – Jeremy Caney Jan 19 '23 at 02:19