Вопрос

I'm building a custom UICollectionViewLayout for a client based on their design specifications to achieve the look and flow they want. There is an issue I'm running into that involves the size of the cell in which I'm positioning.

It is is possible that the cells can be of different sizes and the layout style that I'm going for requires specific spacing requirements so that cells don't overlap each other, which means that during the overloaded -layoutAttributesForItemAtIndexPath: in the layout I'd like to peek the items size so that it can be set and used in calculations properly.

The issue that I'm having is that it appears that the UICollectionView is depending on this method to set the size of the cells because until I set attributes any method I use to fetch a size from a cell will always result in a CGSizeZero.

Is this something I'm going to have to design around? The sizes for the cells in Storyboard aren't accessible (at least with methods that I'm currently aware of) so I haven't been able ot solve this other than to just use a specific width/height (which only works for one of the two possible cells) to test and write the layout code.

Ultimately: How do you fetch the size of a UICollectionViewCell in a UICollectionViewLayout to use in calculations?.

EDIT:

I've tried the following methods:

CGRect frame = [[self.collectionView cellForItemAtIndexPath:path] frame];
CGSize size = [[self.collectionView cellForItemAtIndexPath:path] intrinsicContentSize];
CGRect frame = [[[self.collectionView cellForItemAtIndexPath:path] contextView] frame];
Это было полезно?

Решение

I've never found a way to avoid duplicating size calculations in code. Here's what I usually do:

Getting the size in the layout:

The layout can talk to its collection view to find sizes (as well as things like minimum inter-item spacing, etc.). You can do something like this in -prepareLayout or -layoutAttributesForItemAtIndexPath::

if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
    id<UICollectionViewDelegateFlowLayout> delegate = (id<UICollectionViewDelegateFlowLayout>) self.collectionView.delegate;
    size = [delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
}
else {
    size = self.itemSize;
}

Now, your collection view delegate just needs to be able to respond to collectionView:layout:sizeForItemAtIndexPath:, which would have been necessary even without a custom layout class.

Calculating the size in the first place:

It is a little troublesome to provide correct sizes sometimes. There's no magical way to get the size of a cell without actually instantiating it and laying it out, so I always end up writing that code myself. Most of my collection view cells have a class method:

+ (CGSize)sizeWithItem:(id<MyDisplayableItem>)item;

I call that from my collection view delegate when I need the size a cell will take:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    Note* note = self.notes[indexPath.item];
    CGSize result = [CSDetailNoteCell sizeWithNote:note];
    // or [MyCell sizeWithItem:myItem andMyOtherItem:somethingElse]
    return result;
}

Unfortunately, the +sizeWithItem: method doesn't come for free, but at least it keeps the size calculation encapsulated within the Cell class. I tend to store a constant in the class representing the size of things that never change (padding, maybe an image). Then, I combine that fixed size with the things that need to be calculated dynamically to find the final size. The dynamic stuff usually includes some calls to NSString's -boundingRectWithSize:options:attributes:context:, etc.

Sample size calculation:

This example uses a cell whose width is always 320, but I think it's enough to get the point across. This is code from CSDetailNoteCell.m.

static CGFloat const CSDetailNoteCellWidth = 320.f;
static CGFloat const CSDetailNoteCellHeightConstant =
        10 + // top padding
        16 + // date label height
        0 + // padding from date label to textview
        8 + // textview top padding
        8 + // textview bottom padding
        22 // bottom padding
;

...

+ (CGFloat)textWidth {
    return CSDetailNoteCellTextViewWidth - 10; // internal textview padding
}

+ (CGSize)sizeWithNote:(CSNote *)note {
    CGFloat textHeight = [self _textHeightWithText:note.text];
    CGSize result = CGSizeMake(CSDetailNoteCellWidth, CSDetailNoteCellHeightConstant + textHeight);
    return result;
}

+ (CGFloat)_textHeightWithText:(NSString*)text {
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];

    CGSize maxSize = CGSizeMake(CSDetailNoteCell.textWidth, CGFLOAT_MAX);
    NSDictionary* attributes = @{NSFontAttributeName: CSDetailNoteCell.font, NSParagraphStyleAttributeName: paragraphStyle};
    CGRect boundingBox = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
    CGFloat textHeight = ceilf(boundingBox.size.height);
    return textHeight;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top