Crashalot
Crashalot

Reputation: 34523

Unable to call dequeueReusableCellWithReuseIdentifier inside of collectionView(collectionView: collectionViewLayout: sizeForItemAtIndexPath)

The app crashes when calling dequeueReusableCellWithReuseIdentifier inside of collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize.

Note: this question is not a duplicate because dequeueReusableCellWithReuseIdentifier works elsewhere in the UICollectionView, just not within a specific function.

There is no exception message, and Xcode only highlights assembly code so unsure where the problem lies.

The code is below.

The goal is to make the cell height dynamic while the width matches the width of the UICollectionView, much like cells in a UITableView.

1) Why does it crash on dequeueReusableCellWithReuseIdentifier?

2) If you can't use dequeueReusableCellWithReuseIdentifier, how else can you dynamically determine the intrinsic height of the cell?

3) Is there a better way to mimic the sizing properties of a UITableViewCell (i.e., same width as superview but dynamic height)? There are many SO posts on this topic but none provide very clean solutions.

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(MessageCellIdentifier, forIndexPath: indexPath) as! MessageCell

    let cellSize = CGSize(width: view.frame.width, height: cell.frame.height)
    return cellSize
}

Upvotes: 2

Views: 3121

Answers (1)

tacos_tacos_tacos
tacos_tacos_tacos

Reputation: 10585

Closer to understanding the crash

This is a 1:1 replacement of UITVC without pissing Apple off.

First, this is where your callback gets used.

    // if delegate implements size delegate, query it for all items
    if (implementsSizeDelegate) {
        for (NSInteger item = 0; item < numberOfItems; item++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:(NSInteger)section];
            CGSize itemSize = implementsSizeDelegate ? [flowDataSource collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath] : self.itemSize;

            PSTGridLayoutItem *layoutItem = [layoutSection addItem];
            layoutItem.itemFrame = (CGRect){.size=itemSize};
        }

https://github.com/steipete/PSTCollectionView/blob/master/PSTCollectionView/PSTCollectionView.m

Look at attributes assignment and the very last line...

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
    // de-queue cell (if available)
    NSMutableArray *reusableCells = _cellReuseQueues[identifier];
    PSTCollectionViewCell *cell = [reusableCells lastObject];
    PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];

    // ... IMPL ...

    [cell applyLayoutAttributes:attributes];

    return cell;
}

There is much code to go through but there is at least one path where you can fall into infinite recursion...

Edit

From the link I posted below...

Since collectionView:layout:sizeForItemAtIndexPath: is called before cellForItemAtIndexPath:, so we need to initialize a cell and let system use auto layout to calculate height for us. To avoid memory leak, we use a dictionary to cache the cells that are off screen (not shown on screen)

In collectionView:layout:sizeForItemAtIndexPath:, first create or retrieve a cell

var cell: MyCollectionViewCell? = self.offscreenCells[reuseIdentifier] as? MyCollectionViewCell
if cell == nil {
    cell = NSBundle.mainBundle().loadNibNamed("MyCollectionViewCell", owner: self, options: nil)[0] as? MyCollectionViewCell
    self.offscreenCells[reuseIdentifier] = cell
}

Once a cell is initialized, its size is determined by size in xib file, thus, we need configure texts in cell and layoutSubviews, this will let system recalculate the size of cell

// Config cell and let system determine size
cell!.configCell(titleData[indexPath.item], content: contentData[indexPath.item], titleFont: fontArray[indexPath.item] as String, contentFont: fontArray[indexPath.item] as String)
// Cell's size is determined in nib file, need to set it's width (in this case), and inside, use this cell's width to set label's preferredMaxLayoutWidth, thus, height can be determined, this size will be returned for real cell initialization
cell!.bounds = CGRectMake(0, 0, targetWidth, cell!.bounds.height)
cell!.contentView.bounds = cell!.bounds

// Layout subviews, this will let labels on this cell to set preferredMaxLayoutWidth
cell!.setNeedsLayout()
cell!.layoutIfNeeded()

Once cell is updated, call var size = cell!.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) to get the size for this cell.

In cellForItemAtIndexPath:, cell also need configured and layout its subviews

Orig

Well, it doesn't crash when calling the method you named (half of it), it crashes when calling

dequeueReusableCellWithReuseIdentifier(MessageCellIdentifier, 
    forIndexPath: indexPath) as! MessageCell

The problem I see with it is that you are trying to call a method that needs the method you are calling it from to give you an answer. It should produce a stack overflow.

I'm not saying it's impossible but you aren't overriding enough or the right methods I believe.

Check out the https://github.com/honghaoz/Dynamic-Collection-View-Cell-With-Auto-Layout-Demo/blob/master/README.md for an example of what you want, and/or post more code.

Upvotes: 1

Related Questions