openradar-mirror
openradar-mirror copied to clipboard
27519174: UICollectionViewLayout not respecting element attributes z-index
Description
Summary: We’ve got a custom UICollectionViewFlowLayout subclass that consists of a number of decoration views as “section” backgrounds, interactive supplementary views that act as tappable headers to expand/collapse sections, and obviously cells, which are arranged in a section in a simple grid flow layout. In iOS 8 and 9 this layout has worked perfectly, but using it on iOS 10, we noticed that cells were missing. In debugging this problem we’ve found that it appears that in iOS 10 the UICollectionViewLayoutAttributes zIndex property isn’t being properly obeyed, and some cells, and supplementary views (headers) are being presented behind our decoration view backgrounds.
Setting the section decoration view background to translucent shows the cells happily placed underneath. Also, inspecting the views in with the View Hierarchy debugger, shows the cells on the stack, but not in the proper hierarchy.
Steps to Reproduce:
- Implement UICollectionViewFlowLayout which lays out a single decoration view for each section, supplementary view for each section, and multiple cells.
- Provide a zIndex value of -1 (or 0) to the decoration view attributes in layoutAttributesForDecorationViewOfKind.
- Provide a zIndex value of 10 to the cell attributes in layoutAttributesForItemAtIndexPath.
- Provide a zIndex value of 20 to the section header (supplementary view) attributes in layoutAttributesForSupplementaryViewOfKind.
- While running, if the layout is invalidated/reloaded, cells/supplementary views will randomly appear behind the decoration view, even though elements of both kinds have a higher int value for zIndex
Expected Results: When the layout is invalidated/reloaded, cells and supplementary/decoration views are presented top to bottom layout order obeys the statement of “Items with higher index values appear on top of items with lower values.” as described in the UICollectionViewLayoutAttributes documentation.
Actual Results: When the layout is invalidated/reloaded, cells/supplementary views will randomly appear behind the decoration view, even though elements of both kinds have a higher int value for zIndex.
Version: iOS 10.0 (14A5309d)
Notes: Attached are two screenshots captured during the expand/collapse (ie. add/delete of section items) animations, showing cells being presented under the decoration view background.
Configuration: iPhone 6s / iPad Air 2, iOS Sim
Attachments: 'section-expand.png' and 'section-collapsed.png' were successfully uploaded.
Product Version: iOS 10.0 Created: 2016-07-25T05:37:50.166650 Originated: 2016-07-25T00:00:00 Open Radar Link: http://www.openradar.me/27519174
In my experience attribute zIndex is obeyed when attributes are returned from layoutAttributesForElementsInRect(...). However attributes returned from layoutAttributesForSupplementaryViewOfKind and layoutAttributesForItemAtIndexPath are for some reason ignored. My current workaround is to explicitly set the layer.zPosition in code when returning the cell for item and supplementary view in the UICollectionViewDataSource implementation and setting layer.zPosition for the decoration view in the Interface Builder as a custom attribute.
However it's still unpleasant and is a real issue.
I got another workaround. Since all the cells belong to the same superview, calling bringSubviewToFront :
when cell displaying works. Specifically, by looking into Debug View Hierarchy, though UICollectionViewLayout not renders cells according to zIndex, it still shows cells according to the reverse order that each subview being added to it's super view.
I find that it is best to set the zIndex of the cell layer either in the
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
...
cell.layer.zPosition = indexPath.item;
return cell;
}
or in the cell class:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
self.layer.zPosition = layoutAttributes.zIndex;
}
In my case the problem caused overlapping cells by background supplementary view. My workaround for this is:
public func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
// workaround for https://github.com/lionheart/openradar-mirror/issues/15453
collectionView.sendSubview(toBack: view)
}
I try the solution from @mahboud and @rafalur, but it don't work. At last, I solve it by this way:
- subclass
UICollectionView
- override
layoutSubviews
func, and send decorationView to back manually
here is the code
class YourCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
var decorationViews: [UIView] = []
self.subviews.forEach { (subview) in
if let decorationView = subview as? YourDecorationReusableView {
decorationViews.append(decorationView)
}
}
decorationViews.forEach { (decorationView) in
self.sendSubview(toBack: decorationView)
}
}
}
Here is my Demo Code
@YK-Unit your solution works great! Thanks so much
i Find tha tit is best to set the zIndex of the cell layer either in the
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; { ... cell.layer.zPosition = indexPath.item; return cell; }
or in the cell class:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { [super applyLayoutAttributes:layoutAttributes]; self.layer.zPosition = layoutAttributes.zIndex; }
I recently experienced z indexing issue with both supplementary / decoration views, this solved the problem perfectly. I will use this in every supplementary / decoration subclass in future!
Thanks so much!