Warning: Uicollectionviewflowlayout Has Cached Frame Mismatch for Index Path 'Abc'

Warning: UICollectionViewFlowLayout has cached frame mismatch for index path 'abc'

This is likely occurring because the flow layout "xyz" is modifying attributes returned by UICollectionViewFlowLayout without copying them

And sure enough, that's just what you are doing:

private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
let distance = CGRectGetMidX(attributes!.frame) - self.midX;
var transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, -distance, 0, -self.width);
attributes!.transform3D = CATransform3DIdentity;
return attributes
}

I expect that if you simply say:

let attributes = 
super.layoutAttributesForItemAtIndexPath(indexPath).copy()
as! UICollectionViewLayoutAttributes

or similar, the problem will go away.

Warning in use custom collectionViewFlowLayot

Do like this, to copy the attributes

UICollectionViewLayoutAttributes *attributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];

This question has solved in this issue:
Warning: UICollectionViewFlowLayout has cached frame mismatch for index path 'abc'

Strange disappearing items when scrolls

Sorry, all, but i found reason and work around.

This happen only on device not on simulator

Look at three CGRects

iPhone 5S

(CGRect) oldFrame = (origin = (x = -0.000000000000056843418860808015, y = 0), size = (width = 320.00000000000006, height = 314.00000000000006))

iPhone 5C

(CGRect) oldFrame = (origin = (x = 0, y = 0), size = (width = 320, height = 314))

Simulator Intel Core

(CGRect) oldFrame = (origin = (x = 0, y = 0), size = (width = 320, height = 314))

To all of them i apply rotation transform via

CGAffineTransformMakeRotation((CGFloat)M_PI)

First it's on iPhone 5S ARM Apple A7 CPU

Second it's on iPhone 5C ARM Apple A6 CPU

Third it's on Simulator at Intel Core iX processor.

So, my work around is:

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *retAttr = [super layoutAttributesForItemAtIndexPath:indexPath];

if (CGAffineTransformIsIdentity(retAttr.transform)) {
retAttr.transform = CGAffineTransformMakeRotation((CGFloat)M_PI);
}

CGRect oldFrame = retAttr.frame;

oldFrame.origin.x = retAttr.frame.origin.x > 0 ?: 0;

retAttr.frame = oldFrame;

return retAttr;
}

UICollectionView custom flow layout crashes on scrolling

You need to invalidate the existing layout before updating, see the end of the error message:

without invalidating the layout

You should override the method at the subclass of UICollectionViewFlowLayout:

override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}

For more reference see Apple Documentation for UICollectionViewLayout

Specifying one Dimension of Cells in UICollectionView using Auto Layout

It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).

On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)

However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.

There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.

In your UICollectionViewDelegateFlowLayout, you implement a method like this:

func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width

// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}

This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.

The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize, which is like so:

 /*
Computes the size the cell will need to be to fit within targetSize.

targetSize should be used to pass in a width.

the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.

*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {

// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth

// assert: targetSize.width has the required width of the cell

// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame

// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width

// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width

// step3: compute how tall the cell needs to be

// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

// assert: computedSize has the needed height for the cell

// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)

// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth

return newSize
}

(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)

A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize in order to get the cell to compute its height while taking the label into account.

Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.

My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.

Example project showing it at work.

But why not just use self-sizing cells?

In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think this is partly because the self-sizing cell mechanism is still buggy.

But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.

And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.

And what about preferredLayoutAttributesFittingAttributes: ?

The preferredLayoutAttributesFittingAttributes: method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.

And what's up with systemlayoutSizeFittingSize:?

You're right the docs are confusing.

The docs on systemLayoutSizeFittingSize: and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: both suggest that you should only pass UILayoutFittingCompressedSize and UILayoutFittingExpandedSize as the targetSize. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize parameter.

In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize seems to return the exact same values as UILabel.sizeThatFits. This is suspicious, given that the argument to systemLayoutSizeFittingSize is supposed to be a rough target and the argument to sizeThatFits: is supposed to be a maximum circumscribing size.

More Resources

While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:

  • http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
  • http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
  • code for WWDC2014 session 232, "Advanced User Interfaces with Collection Views"

Left Align Cells in UICollectionView

The other solutions in this thread do not work properly, when the line is composed by only 1 item or are over complicated.

Based on the example given by Ryan, I changed the code to detect a new line by inspecting the Y position of the new element. Very simple and quick in performance.

Swift:

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)

var leftMargin = sectionInset.left
var maxY: CGFloat = -1.0
attributes?.forEach { layoutAttribute in
if layoutAttribute.frame.origin.y >= maxY {
leftMargin = sectionInset.left
}

layoutAttribute.frame.origin.x = leftMargin

leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
maxY = max(layoutAttribute.frame.maxY , maxY)
}

return attributes
}
}

If you want to have supplementary views keep their size, add the following at the top of the closure in the forEach call:

guard layoutAttribute.representedElementCategory == .cell else {
return
}

Objective-C:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
CGFloat maxY = -1.0f;

//this loop assumes attributes are in IndexPath order
for (UICollectionViewLayoutAttributes *attribute in attributes) {
if (attribute.frame.origin.y >= maxY) {
leftMargin = self.sectionInset.left;
}

attribute.frame = CGRectMake(leftMargin, attribute.frame.origin.y, attribute.frame.size.width, attribute.frame.size.height);

leftMargin += attribute.frame.size.width + self.minimumInteritemSpacing;
maxY = MAX(CGRectGetMaxY(attribute.frame), maxY);
}

return attributes;
}

How to center align the cells of a UICollectionView?

I think you can achieve the single line look by implementing something like this:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 100, 0, 0);
}

You will have to play around with that number to figure out how to force the content into a single line. The first 0, is the top edge argument, you could adjust that one too, if you want to center the content vertically in the screen.



Related Topics



Leave a reply



Submit