My UICollectionView doesn't get populated with any cells when VoiceOver is on

Viewed 386

I'm hitting a peculiar problem with accessibility in iOS 13.

I have a calendar that's implemented as a UICollectionView. If VoiceOver is turned on, the collection view is empty - it simply doesn't get populated with any UICollectionViewCells.

I've overridden the correct UICollectionViewDataSource calls. numberOfSections and collectionView(numberOfItemsInSection) are called, and returns correct nonzero values. But collectionView(cellForItemAt) is never called. That's what doesn't make any sense to me; it knows how many sections and how many items I have, but it doesn't care to ask about the items.

If I turn off VoiceOver, then it'll call collectionView(cellForItemAt) and it populates my collection view properly. And in iOS 12, everything works fine whether or not VoiceOver is on.

What am I missing, or how can I debug this? How could the state of VoiceOver affect whether my collection view gets populated?

5 Answers

We reported the problem to our Apple representative and got a response. What's happening is that in iOS 13, VoiceOver asks the collection view for information about a larger rect than is visible; this was an intentional change to support certain types of collection views whose content size is not known up-front. So it was calling our override of layoutAttributesForElements(in rect:) with a large negative Y values for the rect, and we didn't have any elements out there.

Our fix was to ignore the Y value of the rect we were given, and instead use a Y value of 0. iOS was then able to find our collection view cells.

I had the exact same issue, however Brian's answer didn't work for me. Instead I found it was setting the delegate and datasource of the UICollectionView to nil that caused the issue. Removing the lines that set it to nil fixed it.

I had this issue (UICollectionView fine normally, but blank with VoiceOver on) and couldn't find the answer anywhere (none of the posted responses where the issue I was hitting).

My problem turned out to be for a vertical UICollectionView you can set the contentSize to (width:0, height:something) and it works perfectly. But with VoiceOver on it really doesn't like that width:0 and doesn't attempt to load anything. The fix is just to set width to any value, even width:1, and suddenly it will start working again.

Issue on iOS 14.2 (didn't test other iOS versions)

I was having the same issue, however the previously posted solutions of normalizing the rect passed into layoutAttributesForElements(in rect: CGRect) was not working (in fact, we were not even using the passed-in rect value at all -- the method signature in our implementation was layoutAttributesForElements(in _: CGRect)), nor was setting the contentSize on the collectionView directly as Kyle suggested.

What fixed the issue for me was overriding the collectionViewContentSize computed property on the UICollectionViewLayout subclass. Apparently, this method is configured by default to return .zero, which breaks the layout when voiceover is enabled. Still working on figuring out why this happens, but in the meantime adding the following to the the following to the UICollectionViewLayout subclass fixed the issue:

override var collectionViewContentSize: CGSize {
  guard let collectionView = collectionView else { return .zero }
  return collectionView.frame.size
}

It looks like apple sets the origin used for layoutAttributesForElements(in rect: CGRect) to negative device height for some reason. I solved this by using this normalisation code.

func normalizeRect(_ rect: CGRect) -> CGRect {
    guard rect.origin.x < 0, rect.origin.y < 0 else { return rect }
    let normalizedSize = CGSize(width: rect.origin.x + rect.width, height: rect.origin.y + rect.height)
    let value = CGRect(origin: .zero, size: normalizedSize)
    return value
}


Example iPhone 12max:
  * expected rect (0.0, 0.0, 428.0, 926.0)
  * given rect (-926.0, -926.0, 1354.0, 2778.0)
  * normalized rect: (0, 0, 428 (-926 + 1354), 1852 (-926 + 2278 == 2 * 926)) 
Related