-->

How do I accurately provide elements in a rect for

2020-07-26 03:11发布

问题:

I'm using UICollectionView to build UI that can display elements in a grid or a vertical list layout. UICollectionViewFlowLayout doesn't play well with a full-width list layout, so I'm writing my own UICollectionViewLayout subclass. And the rows are self-sizing, so that they can have multiline labels and match their font size to the system font size setting and grow/shrink as needed

Internally, it has a collection of layout attributes for all rows. In prepareLayout I walk over all index paths in the data source and create layout attributes with their frames set for an estimated row height.

In shouldInvalidateLayoutForPreferredLayoutAttributes, I return YES if the height is different than the value I have for that row in my collection. Then in invalidationContextForPreferredLayoutAttributes, I invalidate all items after the index path of the passed-in preferredAttributes, because a height change in this row affects the vertical position of all subsequent rows. Then prepareLayout gets called again and I update the frames of everything as necessary.

The problem comes with this sequence of events:

  • layoutAttributesForElementsInRect gets called. I walk over all my attributes and check if CGRectIntersectsRect(attr.frame, rect).
  • shouldInvalidateLayoutForPreferredLayoutAttributes gets called for each row that is about to appear. Some of them end up smaller than the estimated height they had originally.

If the total row shrinkage is enough, a row or two that weren't within the rect for layoutAttributesForElementsInRect get moved up enough that they should appear. For example, if the rect was y values 0 through 1334, a row with y origin 1340 before the calls to shouldInvalidateLayoutForPreferredLayoutAttributes could now be at 1300. But the layout object already "knows" what rows are supposed to be onscreen, so those rows just don't show up, and there are holes in my list.

I might be able to work around this by returning extra rows from layoutAttributesForElementsInRect (by expanding the rect I get by 100 points vertically or something). But that's a hack, and this seems like something that the APIs should have a way to address. But I don't get another call to layoutAttributesForElementsInRect after self-sizing is done, and I don't see a way to ask for it to be called again via UICollectionViewLayoutInvalidationContext.

So...am I missing something obvious in the API? Is this a problem that shouldn't be happening, which means I'm approaching something wrong? It seems like there's no way to accurately answer layoutAttributesForElementsInRect before self-sizing has happened...?

Example code illustrating the problem is here on GitHub.

回答1:

I've discovered that having extra invalidations in invalidationContextForPreferredLayoutAttributes when you have self-sizing supplementary views will cause a bug where you get layout gaps. I've made the same mistake and others have too because it seems logical to invalidate the items below and item being self-sized. My guess is that the collection view handles this for you.

These are the lines to remove in your sample project:

[result invalidateItemsAtIndexPaths:rowPaths];
[result invalidateSupplementaryElementsOfKind:UICollectionElementKindSectionHeader atIndexPaths:headerPaths];

I've filed a documentation update Radar with Apple about this. http://www.openradar.me/35833995