9
votes

I would like to take advantage of the dynamic UITableViewCell height new in iOS 8. I need to place a UICollectionView within a UITableViewCell. I want to ensure all of the cells in the collection view are visible on screen, so the table cell should increase in height to fit the collection view. I almost have this working. I just haven't been able to get the table cell to be the right size - it's either too long or too short in height, and some layout issues are visible until I interact with the table (more on that below).

I've set up auto layout constraints for the collection view to the table's cell's contentView: leading, trailing, top, and bottom. I then created a height constraint on the collection view so I can update its constant on the fly after calculating the appropriate height to fit all cells in the collection view (because I don't think there's a way to do that automatically).

Here is the rest of the setup.

viewDidLoad {
    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 44
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    let cellDimension = self.collectionView.frame.size.width / 7 //always 7 columns

    let flowLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    flowLayout.itemSize = CGSizeMake(cellDimension, cellDimension)

    self.collectionViewHeightConstraint.constant = cellDimension * 4 //always 4 rows
}

I have not implemented heightForRowAtIndexPath.

This setup results in conflicting vertical constraints. I've tried reducing the priority of the constraints and changing the relation in a bunch of combinations, but none resulted in the desired behavior. If I reduce the priority of the height constraint to 999, the table view cell is too tall at first, but after I scroll up and down the table view the table cell height updates to the expected height. With some other combinations, the result is either the table cell is too short yet the full collection view bleeds out, the table cell is too short and the collection view is cut off, or the table cell is too tall, based on what priority and relations I've applied to the constraints. Additionally, the collection view cells are not displayed at the proper size while the view is animating in (but they're properly adjusted by the time animation finishes), or if I reload the table the collection view cells are the incorrect size until I scroll the table.

How can I resolve these appearance issues to obtain a dynamic table view cell height that contains a fully visible collection view featuring dynamic cell size based on the display's width?

3
I would suggest moving the rowHeight setting "self.tableView.rowHeight = " from ViewDidLoad to ViewWillAppear instead, You might want to check this question also. linkKevin Horgan
@KevinHorgan That didn't make any noticeable difference.Jordan H
In order to make autolayout work properly in UITableViewCell you need to add constraints for all layouts in containerView without missing.Otherwise autolayouts won't work properly.Check the following link which gives some detailed information of autolayout setup for tableviewcell stackoverflow.com/questions/29231390/…Ram Vadranam
@Ram The constraints are all defined, top bottom leading trailing from the collection view to the contentView.Jordan H
AFAICS the answer is more complicated than anyone here has mentioned. Check out stackoverflow.com/questions/24126708/…xaphod

3 Answers

1
votes

From what i can understand from you code in setting up the collectionView cells and size, it seems to me that you want to have square collection cells, and have 4 rows of 7 cells and all fully visible.

If you add constraints to the collectionView that is in the cell to all 4 margins(top, bottom, left and right) and then add an Aspect Ratio Constraint of 7:4 to the collectionView, the tableview should be able to calculate cell height automatically for you at the right size.

0
votes

Here's how I'd approach this, and I think it can be done all in IB.

First, I'd set minimum sizes for the CV cells then I'd set the hugging priority of the CV to hug its contents as tightly as possible. That should guarantee that, all external influences apart, the CV will try to have the smallest possible size that makes all of its cells visible.

Next, I'd play the same game with the TBV cell's content view and the CV, that is, I'd set the hugging priority of the TBV cell's content view to hug the CV as tightly as possible. Again, this should enforce that, external influences neglected, the TBV cell should keep the smallest size it needs to keep in order to display the CV in full.

0
votes

Have a look at how forkingdog has solved the same problem of dynamic heights of tableview cells here > https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

You should be able to switch out the imageview for uicollectionview. enter image description here

In his tableview - rather than using the out of the box estimated row height - you'll need something like

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [tableView fd_heightForCellWithIdentifier:@"FDFeedCell" configuration:^(FDFeedCell *cell) {
        cell.entity = self.feedEntities[indexPath.row];
    }];
}



#import "UITableView+FDTemplateLayoutCell.h"
#import <objc/runtime.h>

@implementation UITableView (FDTemplateLayoutCell)

- (id)fd_templateCellForReuseIdentifier:(NSString *)identifier;
{
    NSAssert(identifier.length > 0, @"Expects a valid identifier - %@", identifier);

    NSMutableDictionary *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
    if (!templateCellsByIdentifiers) {
        templateCellsByIdentifiers = @{}.mutableCopy;
        objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
    if (!templateCell) {
        templateCell = [self dequeueReusableCellWithIdentifier:identifier];
        templateCellsByIdentifiers[identifier] = templateCell;
    }

    return templateCell;
}

- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration
{
    // Fetch a cached template cell for `identifier`.
    UITableViewCell *cell = [self fd_templateCellForReuseIdentifier:identifier];

    // Reset to initial height as first created, otherwise the cell's height wouldn't retract if it
    // had larger height before it gets reused.
    cell.contentView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.frame), self.rowHeight);

    // Manually calls to ensure consistent behavior with actual cells (that are displayed on screen).
    [cell prepareForReuse];

    // Customize and provide content for our template cell.
    if (configuration) {
        configuration(cell);
    }

    // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
    // of growing horizontally, in a flow-layout manner.
    NSLayoutConstraint *tempWidthConstraint =
    [NSLayoutConstraint constraintWithItem:cell.contentView
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:1.0
                                  constant:CGRectGetWidth(self.frame)];
    [cell.contentView addConstraint:tempWidthConstraint];

    // Auto layout does its math
    CGSize fittingSize = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    [cell.contentView removeConstraint:tempWidthConstraint];

    // Add 1px extra space for separator line if needed, simulating default UITableViewCell.
    if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
        fittingSize.height += 1.0 / [UIScreen mainScreen].scale;
    }

    return fittingSize.height;
}

@end