Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically resize UICollectionView's supplementary view (containing multiline UILabel's)

Inside a UICollectionView's supplementary view (header), I have a multiline label that I want to truncate to 3 lines.

enter image description here

When the user taps anywhere on the header (supplementary) view, I want to switch the UILabel to 0 lines so all text displays, and grow the collectionView's supplementary view's height accordingly (preferably animated). Here's what happens after you tap the header:

enter image description here

Here's my code so far:

// MyHeaderReusableView.m

// my gesture recognizer's action
- (IBAction)onHeaderTap:(UITapGestureRecognizer *)sender
{
    self.listIntro.numberOfLines = 0;

    // force -layoutSubviews to run again
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.listTitle.preferredMaxLayoutWidth = self.listTitle.frame.size.width;
    self.listIntro.preferredMaxLayoutWidth = self.listIntro.frame.size.width;
    [self layoutIfNeeded];

    CGFloat height = [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    self.frame = ({
        CGRect headerFrame = self.frame;
        headerFrame.size.height = height;
        headerFrame;
    });

    NSLog(@"height: %@", @(height));
}

When I log height at the end of layoutSubviews, its value is 149 while the label is truncated and numberOfLines is set to 3. After tapping the headerView, setting numberOfLines to 0, and forcing a layout pass, height then gets recorded as 163.5. Great!

The only problem is that the entire headerView doesn't grow, and the cells don't get pushed down.

How can I dynamically change the height of my collectionView's supplementary view (preferably animated)?

I'm aware of UICollectionViewFlowLayout's headerReferenceSize and collectionView:layout:referenceSizeForHeaderInSection: but not quite sure how I'd use them in this situation.

like image 646
djibouti33 Avatar asked May 01 '15 17:05

djibouti33


1 Answers

I got something working, but I'll admit, it feels kludgy. I feel like this could be accomplished with the standard CollectionView (and associated elements) API + hooking into standard layout/display invalidation, but I just couldn't get it working.

The only thing that would resize my headerView was setting my collection view's flow layout's headerReferenceSize. Unfortunately, I can't access my collection view or it's flow layout from my instance of UICollectionReusableView, so I had to create a delegate method to pass the correct height back.

Here's what I have now:

// in MyHeaderReusableView.m
//
// my UITapGestureRecognizer's action
- (IBAction)onHeaderTap:(UITapGestureRecognizer *)sender
{
    self.listIntro.numberOfLines = 0;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.listTitle.preferredMaxLayoutWidth = self.listTitle.frame.size.width;
    self.listIntro.preferredMaxLayoutWidth = self.listIntro.frame.size.width;

    CGFloat height = [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    self.frame = ({
        CGRect headerFrame = self.frame;
        headerFrame.size.height = height;
        headerFrame;
    });

    if (self.resizeDelegate) {
        [self.resizeDelegate wanderlistDetailHeaderDidResize:self.frame.size];
    }
}

// in my viewController subclass which owns the UICollectionView:
- (void)wanderlistDetailHeaderDidResize:(CGSize)newSize
{
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;

    // this is the key line
    flowLayout.headerReferenceSize = newSize;

    // this doesn't look beautiful but it's the best i can do for now. I would love for just the bottom of the frame to animate down, but instead, all the contents in the header (the top labels) have a crossfade effect applied.    
    [UIView animateWithDuration:0.3 animations:^{
        [self.collectionView layoutIfNeeded];
    }];
}

Like I said, not the solution I was looking for, but a working solution nonetheless.

like image 91
djibouti33 Avatar answered Oct 23 '22 21:10

djibouti33