2

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
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • One question, you never use the `cell` parameter inside the `sizeForItemAtIndexPath` method? If so why you need it... – J.Wang Apr 04 '16 at 00:53
  • @J.Wang this was a simplified version, the `cell` parameter is used to determine the cell height. question updated. – Crashalot Apr 04 '16 at 01:26
  • Possible duplicate of [iOS Assertion Failure in UICollectionView](http://stackoverflow.com/questions/12599863/ios-assertion-failure-in-uicollectionview) – Basheer_CAD Apr 04 '16 at 01:46
  • @Basheer_CAD not a dupe, please see the question for clarification. – Crashalot Apr 04 '16 at 01:52

1 Answers1

1

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.

tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126
  • ah, makes sense. so you're saying dequeueReusableCellWithReuseIdentifier relies on collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize? that would explain why it crashes. :) – Crashalot Apr 04 '16 at 01:27
  • I believe that it does but I am still looking for proof :) – tacos_tacos_tacos Apr 04 '16 at 01:40
  • I am less sure that my statement is literally true but more sure that is functionally true, as in you will not be happy if you go down the path of trying to make that call work. Ive always understood that it implements a cache based on visibility and nearness, but *how can it do so when it doesn't know the critical dimension on height*... see edit to answer above I just posted the chinese fellow's blog from gist ,t the salient bits – tacos_tacos_tacos Apr 04 '16 at 01:47