Multiple supplementary views in a UICollectionViewFlowLayout? - ios

I've got a fairly standard grid layout using a subclass of UICollectionViewFlowLayout. When the user taps a cell I move the cells in the subsequent rows down to make space for a detail view that will appear. It looks like this:
grid mockup
In that detail view I want to show another view with related data, similar to how iTunes shows album details. The existing layout has headers for each section and I'm currently slapping the detail view into place, manually managing the frame's position. This gets tricky with rotations and cells moving around.
How can I convince the layout to handle the detail's position by treating it as a supplementary view? My controller is configured correctly to display the detail as a supplementary view, same for the layout.

Solved the problem. In broad strokes, here's what works for me:
Create a subclass of UICollectionViewLayoutAttributes and register it in your layout by overriding the layoutAttributesClass function.
In layoutAttributesForElementsInRect: retrieve all standard layout attributes with [super layoutAttributesForElementsInRect:rect];.
Copy that array into a mutable array and append another set of attributes for the supplementary view doing something like [attributesCopy addObject:[self layoutAttributesForSupplementaryViewOfKind:YourSupplementaryKind atIndexPath:indexPathForTappedCell]];
In layoutAttributesForSupplementaryViewOfKind:atIndexPath: get the attributes for your view with something like
YourLayoutAttributes *attributes = [YourLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];
Test that the elementKind matches the type of supplementary view you want to generate
Retrieve the cell's layout attributes using [self layoutAttributesForItemAtIndexPath:indexPath];
Change the frame from the cell's attributes to suit the needs of your supplementary view
Assign the new frame to the supplementary attributes
The important part to note is you can't skip subclassing UICollectionViewLayoutAttributes. Doing asking super (a UICollectionViewFlowLayout instance) for attributes of a supplemental view for anything but standard header or footers will return nil. I couldn't find any concrete documentation on this behavior so I might be wrong but in my experience it was the subclassed attributes that solved my problems.
Your code should look something like this:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *allAttributesInRect = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *attributes = NSMutableArray.array;
for (UICollectionViewLayoutAttributes *cellAttributes in allAttributesInRect)
{
// Do things with regular cells and supplemental views
}
if (self.selectedCellPath)
{
UICollectionViewLayoutAttributes *detailAttributes = [self layoutAttributesForSupplementaryViewOfKind:SomeLayoutSupplimentaryDetailView atIndexPath:self.selectedCellPath];
[attributes addObject:detailAttributes];
}
return attributes.copy;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
SomeLayoutAttributes *attributes = [SomeLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];
if ([elementKind isEqualToString:SomeLayoutSupplimentaryDetailView])
{
UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
CGRect frame = cellAttributes.frame;
frame.size.width = CGRectGetWidth(self.collectionView.frame); // or whatever works for you
attributes.frame = frame;
}
return attributes;
}
Your UICollectionViewLayoutAttributes subclass doesn't necessarily need any extra properties or functions but it's a great place to store data specific to that view for configuration use after you retrieve the view using dequeueReusableSupplementaryViewOfKind:forIndexPath:.

Related

how to develop a custom UICollectionViewLayout that has staggered columns with self-sizing cells?

I'm working on the iOS version of an app I already developed on Android. This app has the following 2 column grid of self-sizing (fixed width but variable height) cells:
Achieving this in the Android version was easy because Google provides a StaggeredGridLayoutManager for its RecyclerView. You specify the number of columns and the direction of the scroll and you are done.
The default UICollectionView layout UICollectionViewFlowLayout doesn't allow the staggered layout I'm looking for, so I have to implement a custom layout. I have watched 2 WWDC videos that talk about this topic (What's New in Table and Collection Views and Advanced User Interfaces with Collection Views) and I more or less have an idea of how it should be implemented.
Step 1. First an approximation of the layout is computed.
Step 2. Then the cells are created and sized with autolayout.
Step 3. Then the controller notifies the of the cell sizes so the layout is updated.
My doubts come when trying to code these steps. I found a tutorial that explains the creation of a custom layout with staggered columns, but it doesn't use autolayout to obtain the size of the cells. Which leaves me with the following questions:
In step 2, how and when can I obtain the cell size?
In step 3, how and when can I notify the layout of the changes?
I want to point out that, as you have mentioned, RayWenderlich PinInterest Layout is exactly the tutorial that'll help you achieve this layout.
To answer your questions - with regards to the tutorial:
In step 2, how and when can I obtain the cell size?
To get the cell height, a delegate method was implemented that was called in the prepareLayout method of the custom UICollectionViewLayout. This method is called once (or twice, I just attempted to run it with a print statement, and I got two calls). The point of prepareLayout is to initialize the cell's frame property, in other words, provide the exact size of each cell. We know that the width is constant, and only the height is changing, so in this line of prepareLayout:
let cellHeight = delegate.collectionView(collectionView!,
heightForItemAtIndexPath: indexPath, withWidth: width)
We obtain the height of the cell from the delegate method that was implemented in the UICollectionViewController. This happens for all the cells we want to show in the collectionView. After obtaining and modifying the height for each cell, we cache the result so we can inspect it later.
Afterwards, for the collectionView to obtain the size of each cell on screen, all it needs to do is query the cache for the information. This is done in layoutAttributesForElementsInRect method of your custom UICollectionViewLayout class.
This method is called automatically by the UICollectionViewController. When the UICollectionViewController needs layout information for cells that are coming onto the screen (as a result of scrolling, for instance, or upon first load), you return the attributes from the cache that you've populated in prepareLayout.
In conclusion to your question: In step 2, how and when can I obtain the cell size?
Answer: Each cell size is obtained within the prepareLayout method of your custom UICollectionViewFlowLayout, and is calculated early in the life cycle of your UICollectionView.
In step 3, how and when can I notify the layout of the changes?
Note that the tutorial does not account for new cells to be added at runtime:
Note: As prepareLayout() is called whenever the collection view’s layout is invalidated, there are many situations in a typical implementation where you might need to recalculate attributes here. For example, the bounds of the UICollectionView might change – such as when the orientation changes – or items may be added or removed from the collection. These cases are out of scope for this tutorial, but it’s important to be aware of them in a non-trivial implementation.
Like he wrote, it's a non trivial implementation that you might need. There is, however, a trivial (very inefficient) implementation that you might adopt if your data set is small (or for testing purposes). When you need to invalidate the layout because of screen rotation or adding/removing cells, you can purge the cache in the custom UICollectionViewFlowLayout to force prepareLayout to reinitialize the layout attributes.
For instance, when you have to call reloadData on the collectionView, also make a call to your custom layout class, to delete the cache:
cache.removeAll()
I realise this is not a complete answer, but some pointers regarding your steps 2 and 3 may be found in the subclassing notes for UICollectionViewLayout.
I presume you have subclassed UICollectionViewFlowLayout since off the top of my head I believe this is a good starting point for making adjustments to the layout to get the staggered appearance you want.
For step 2 layoutAttributesForElementsInRect(_:) should provide the layout attributes for the self sized cells.
For step 3 your layout will have shouldInvalidateLayoutForPreferredLayoutAttributes(_:withOriginalAttributes:) called with the changed cell sizes.
In step 2, how and when can I obtain the cell size?
You need to calculate height of each cell in prepareLayout() method. Result of calculation for each cell should be assigned to UICollectionViewLayoutAttributes variable, and than put it into collection NSDictionary, where key would be NSIndexPath(of each cell), and value would be UICollectionViewLayoutAttributes variable.
Example:
- (void)prepareLayout {
[_layoutMap removeAllObjects];
_totalItemsInSection = [self.collectionView numberOfItemsInSection:0];
_columnsYoffset = [self initialDataForColumnsOffsetY];
if (_totalItemsInSection > 0 && self.totalColumns > 0) {
[self calculateItemsSize];
NSInteger itemIndex = 0;
CGFloat contentSizeHeight = 0;
while (itemIndex < _totalItemsInSection) {
NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:0];
NSInteger columnIndex = [self columnIndexForItemAtIndexPath:targetIndexPath];
// you need to implement this method and perform your calculations
CGRect attributeRect = [self calculateItemFrameAtIndexPath:targetIndexPath];
UICollectionViewLayoutAttributes *targetLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:targetIndexPath];
targetLayoutAttributes.frame = attributeRect;
contentSizeHeight = MAX(CGRectGetMaxY(attributeRect), contentSizeHeight);
_columnsYoffset[columnIndex] = #(CGRectGetMaxY(attributeRect) + self.interItemsSpacing);
_layoutMap[targetIndexPath] = targetLayoutAttributes;
itemIndex += 1;
}
_contentSize = CGSizeMake(self.collectionView.bounds.size.width - self.contentInsets.left - self.contentInsets.right,
contentSizeHeight);
}
}
Don't forget to implement following methods:
- (NSArray <UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [NSMutableArray new];
for (UICollectionViewLayoutAttributes *layoutAttributes in _layoutMap.allValues) {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
[layoutAttributesArray addObject:layoutAttributes];
}
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return _layoutMap[indexPath];
}
These methods would be triggered once you call reloadData() mehtod or invalidateLayout().
In step 3, how and when can I notify the layout of the changes?
Just call self.collectionView.collectionViewLayout.invalidateLayout() and prepareLayout() method would be called once again, so you can recalculate all parameters you need.
You can find my full tutorial about custom UICollectionViewLayout here: https://octodev.net/custom-collectionviewlayout/
Tutorial contains implementation in both languages: Swift and Objective-C.
Would be more than glad to answer all your questions.
The "cell size" is defined by UICollectionViewLayoutAttribute in the layout subclass which mean you can modify it every time you have the chance to touch them. You can set every attributes' size to what you desire.
For example you can do it in layoutAttributesOfElementsInRect(:) , calculate the right size and config all attributes before pass them to collectionView. You can also do it in layoutAttributeOfItemAtIndexPath(:) ,make the calculation when every attribute is created.
Furthermore, consider to provide the desired size by a datasource so every attribute can easily get their size with their index.
For if you want to have the cell size to layout the subviews in a cell, do it in the collectionView delegate method: collectionView:ItemAtIndexPath:
Hope this help.

UITableView with pull to refresh and locked cells like UltraVisual

I'm trying to make something similar to what UltraVisual for iOS already does. I'd like to make my pull-to-refresh be in a cell in-between other cells.
I think the following GIF animation explains it better:
It looks like the first cell fades out when pulling up, while when you pull down and you're at the top of the table, it adds a new cell right below the first one and use it as the pull-to-refresh.
Has anyone done anything similar?
Wrote this one for UV. Its actually way simpler than you're describing. Also, for what its worth, this view was written as a UICollectionView, but the logic still applies to UITableView.
There is only one header cell. Durring the 'refresh' animation, I simply set the content inset of the UICollectionView to hold it open. Then when I've finished with the reload, I animate the content inset back to the default value.
As for the springy fixed header, there's a couple of ways you can handle it. Quick and dirty is to use a UICollectionViewFlowLayout, and modify the attributes in - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
Here's some pseudo code assuming your first cell is the sticky header:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
if ([self contentOffsetIsBelowZero]) {
for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) {
if (attributes.indexPath.item == 0) {
CGPoint bottomOrigin = CGPointMake(0, CGRectGetMaxY(attributes.frame));
CGPoint converted = [self.collectionView convertPoint:bottomOrigin toView:self.collectionView.superview];
height = MAX(height, CGRectGetHeight(attributes.frame));
CGFloat offset = CGRectGetHeight(attributes.frame) - height;
attributes.frame = CGRectOffset(CGRectSetHeight(attributes.frame, height), 0, offset);
break;
}
}
}
Another approach would be to write a custom UICollectionViewLayout and calculate the CGRect's manually.
And finally, the 'fade out' is really nothing more than setting the opacity of the objects inside the first cell as it moves off screen. You can calculate the position of the cell on screen during - (void)applyLayoutAttributes… and set the opacity based on that.
Finally, something to note: In order to do any 'scroll based' updates with UICollectionView, you'll need to make sure - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds returns YES. You can do a simple optimisation check like:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
BOOL shouldInvalidate = [super shouldInvalidateLayoutForBoundsChange:newBounds];
if ([self contentOffsetIsBelowZero]) {
shouldInvalidate = YES;
}
return shouldInvalidate;
}
Again this is mostly pseudo code, so re-write based on your own implementation. Hope this helps!

UICollectionView custom layout with two cells

I've been reading online tutorials on UICollectionView with different layouts. Also looked at a lot of SO Questions on the subject. But it seems what I am looking might be something more simple but I am stuck on how to go forward.
The Goal
I have a UIViewController that is embedded in a UINavigation controller. I am displaying data in a UITableView which includes:1 UIImageView and three UILabels in each cell. The data is fetched from a server and all works nicely.
I then wanted to have a UIButton that, when tapped, would kick off a cool animation that shows the cells transition into a nice grid view.
It suddenly dawned on me that I needed to use a UICollectionView to change between these two cells and ditch the UITableView completely. Tapping the button again, would switch back to the last state (Grid or UITableView style)
The grid cell needs to loose one label - but keep the image.
The problem
I have spent the last two days reading up on UICollectionView and UICollectionViewFlowLayout. I think I could use a Apple's pre-made UICollectionViewFlowLayout and just tweak it a little.
I don't know if I need two custom cells or one cell that changes shape between the two views and how the animations must work.
I'm not looking for the exact code to do this - I just need to know which direction I need to go in and if I need to use two custom cells - and how do I change between the two with animation and not reloading all the data again.
Appreciate any input.
Thanks all.
I finally found a solution that was acceptable to my need. If anyone ever has similar needs - this is how you use two different custom UICollectionViewCell's and how to change between the two different cells / layouts.
First thing is create the customCells in IB - creating the xib
files.
Then set the up as you need
Since my requirement needed the standard flow layout provided by the class UICollectionViewFlowLayout - I just needed to create two custom layouts and tweak them to my needs.
Create two (or more if needed) classes that subclass UICollectionViewFlowLayout
In the implementation - setup the layout as needed. Since I am subclassing the pre-made UICollectionViewFlowLayOut and all I need to do is tweak it - the implementation is pretty simple.
So - for the table view layout I did this:
tableViewFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
This sets each cells width and height to the values I needed. self.minimumLineSpacing sets the spacing between the cells. (Spacing between the cell above / below )
Then for the grid layout:
gridFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 0.1f;
self.minimumLineSpacing = 0.1f;
}
return self;
}
Same as before - however, this time I needed spacing between my cells right edge -
self.minimumInteritemSpacing = 0.1f'
takes care of that.
Right - now to put it all together - in the viewController that has the UICollectionView
viewController.m
// Import the new layouts needed.
#import "GridFlowLayOut.h"
#import "TableViewFlowLayOut.m"
//Create the properties
#property (strong, nonatomic) TableViewFlowLayOut *tableViewLayout;
#property (strong, nonatomic) GridFlowLayOut *grideLayout;
-(void)viewDidLow
{
//Register the two custom collection view cells you created earlier. Make sure you set the correct reuse identifier here.
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"TableViewCell" bundle:nil] forCellWithReuseIdentifier:#"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"GridViewCell" bundle:nil] forCellWithReuseIdentifier:#"GridItemCell"];
}
-(void)viewWillAppear
{
//Create the layout objects
self.grideLayout = [[GridFlowLayOut alloc]init];
self.tableViewLayout = [[TableViewFlowLayOut alloc]init];
//Set the first layout to what it should be
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout];
}
Right - now to change between the layouts with some animation. This is actually very easy to do and only needs a few lines of code -
I called this code in a button method in viewController.m
-(void)changeViewLayoutButtonPressed
{
//BOOl value to switch between layouts
self.changeLayout = !self.changeLayout;
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}
}
And lastly in cellForItemAtIndexPath
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *tableCellIdentifier = #"TableItemCell";
static NSString *gridCellIdentifier = #"GridItemCell";
//BOOL used to detect which layout is active
if (self.gridLayoutActive == NO){
CustomCollectionCellClass *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return tableItemCell;
}else
{
CustomCollectionCellClass *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return gridItemCell;
}
return nil;
}
Of course you will need to conform to the other UICollectionView delegates and setup the remaining stuff.
This actually took me a while to figure out. I really hope it helps others out there.
If anyone wants a demo project - I'll happily create one and upload to GitHub.
For anyone new to UICollectionViews I highly recommend reading Apple's programming guide on the subject - it was this document which lead me to this solution.
Reference:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html

Re-use/Leak of Decoration view in UICollectionViewFlowLayout Subclass

Context:
I have already done the following
1. Watched the relevant videos for UICollectionView from WWDC 2012. Unfortunately they do not discuss much about the decoration views.
2. Implemented a collection view with decoration view. I subclassed the UICollectionViewFlowLayout to include the decoration view.
3. I have read the UICollectionView programming guide from Apple.
4. I understand that the Decoration views are controlled solely by the Layout object.
Problem:
1. When I analyze the code with Instruments I find that my decoration views are causing a memory leak.
2. On further analysis of the code I found that the decoration view is not being reused as I would expect. Every time the decoration view is needed a new one is created.
3. When the collection view deallocs only the last created decoration view gets dellocated and all other decorations views leak.
4. I do not understand how the decoration view will be reused when there is no deque method for it.
Questions:
1. Do we have to manually manage the removal and addition of decoration views from the collection view?
I am confused as to how is the decoration view works in general. Any pointers for this would help. The code for my layout object is as below. In the code I am simply putting a 10 points wide bar at the top of my collection view. Everytime the bar is scrolled off the screen and then brought back into the screen, a new decoration view is allocated. When the collection view is deallocated only the last allocated decoration view gets deallocated. Rest all leak.
#import "CollectionViewFlowLayoutSubclass.h"
#import "CollectionViewDecorationView.h"
#define DECORATION_VIEW_KIND #"DecorationViewShelf"
#implementation CollectionViewFlowLayoutSubclass
- (void)awakeFromNib
{
[super awakeFromNib];
[self registerClass:[CollectionViewDecorationView class] forDecorationViewOfKind:DECORATION_VIEW_KIND];
}
- (void)prepareLayout
{
[super prepareLayout];
}
- (CGSize)collectionViewContentSize
{
return [super collectionViewContentSize];
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* larrayAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray* larrayMutableAttributes = [larrayAttributes mutableCopy];
CGRect lrectFrame = CGRectMake(0, 0, 320, 10);
if (CGRectIntersectsRect(rect, lrectFrame))
{
UICollectionViewLayoutAttributes* lobjLayoutAttributes =
[UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:DECORATION_VIEW_KIND
withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
lobjLayoutAttributes.frame = CGRectMake(0, 0, 320, 10);
[larrayMutableAttributes addObject:lobjLayoutAttributes];
}
else
{
NSLog(#"Rect %# does not intersect %#", NSStringFromCGRect(rect), NSStringFromCGRect(lrectFrame));
}
return [NSArray arrayWithArray:larrayMutableAttributes];
}
#end

UICollectionView setLayout:animated: not preserving zIndex

I've noticed that when calling setLayout:animated in a UICollectionView to switch between two layouts, the currently visible cell doesn't adhere to the zIndex it's layout attributes has been set in layoutAttributesForItemAtIndexPath:.
For example, if I were to have a UICollectionView with UICollectionViewFlowLayout, set it's minimumLineSpacing to a negative number so the cells overlap and then set a zIndex on each cell higher than that of the previous cell, then it appears as if the cells are stacked from the bottom up.
However this breaks if I set the layout to another layout then back to that original layout. It's as if the currently visible cell doesn't listen the zIndex and is placed atop the other cells. If I scroll the cell offscreen then back on it is in the correct place.
I have had the same problem. Switching the layout will disregard the zIndex for the cell.
I have managed to make it "look right" by applying a translation on the z-axis like this:
attributes.transform3D = CATransform3DMakeTranslation(0, 0, indexPath.row);
But it is just a visual fix, if you try to click on the item you will realize that the zIndex is still wrong until it is recycled by scrolling it offscreen.
I've managed to get the behaviour I'm after by using a combination grimfrog and Charlie Elliott's responses.
Charlie Elliott's solution got the correct final outcome for the items in the collection view but there was still a snapping effect on the zIndex during the animation.
grimfrog's solution provided the correct look but had the problem of the zIndex still being incorrect after the layout change, despite looking correct.
The combination of the two, while not a great solution, does work and does use the supported transform and zIndex properties of the UICollectionViewLayoutAttributes
In my layout, I have
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
[attributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attributes, NSUInteger idx, BOOL *stop) {
attributes.zIndex = attributes.indexPath.item + 1;
}];
return attributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
attributes.transform3D = CATransform3DMakeTranslation(0, 0, attributes.indexPath.item);
return attributes;
}
I won't make this as the correct answer just yet as I'm sure there must be another way to solve this, but I'm interested to see if this solves the problem for others as well.
Try:
// In UICollectionViewCell subclass
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
// Setting zPosition instead of relaying on
// UICollectionView zIndex management 'fixes' the issue
self.layer.zPosition = layoutAttributes.zIndex;
}
This bit me too. After several tests I realized that UICollectionView will force selected cells to be on top, regardless of the z-index.
Try setting the z-index in:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath;
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath;
Using #sampage & #grimfrog answers as a starting point, I was able to get a similar situation working
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
attributes.zIndex = path.item;
attributes.transform3D = CATransform3DMakeTranslation(0, 0, path.item);
// other attribute settings
return attributes;
}
My layoutAttributesForElementsInRect: calls layoutAttributesForItemAtIndexPath: when generating the attribute array - so I only needed to include the zIndex and transform3D there.
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.

Resources