Uicollectionview - Dynamic Cell Height

UICollectionView - dynamic cell height?

Here is a Ray Wenderlich tutorial that shows you how to use AutoLayout to dynamically size UITableViewCells. I would think it would be the same for UICollectionViewCell.

Basically, though, you end up dequeueing and configuring a prototype cell and grabbing its height. After reading this article, I decided to NOT implement this method and just write some clear, explicit sizing code.

Here's what I consider the "secret sauce" for the entire article:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForBasicCellAtIndexPath:indexPath];
}

- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath {
static RWBasicCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWBasicCellIdentifier];
});

[self configureBasicCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:sizingCell];
}

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];

CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}


EDIT: I did some research into your crash and decided that there is no way to get this done without a custom XIB. While that is a bit frustrating, you should be able to cut and paste from your Storyboard to a custom, empty XIB.

Once you've done that, code like the following will get you going:

//  ViewController.m
#import "ViewController.h"
#import "CollectionViewCell.h"
@interface ViewController () {

}
@property (weak, nonatomic) IBOutlet CollectionViewCell *cell;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
[self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"cell"];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear...");
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 50;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self sizingForRowAtIndexPath:indexPath];
}
- (CGSize)sizingForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
static CollectionViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [[NSBundle mainBundle] loadNibNamed:@"CollectionViewCell" owner:self options:nil][0];
});
[sizingCell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize cellSize = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
NSLog(@"cellSize: %@", NSStringFromCGSize(cellSize));
return cellSize;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
[cell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
return cell;
}
@end

The code above (along with a very basic UICollectionViewCell subclass and associated XIB) gives me this:

Sample Image

Collectionview dynamic height change with constraints not working in Swift

Add this to your table's cellForRow and check if it works:

  cell.frame = tableView.bounds
cell.layoutIfNeeded()
cell.attetchmentsCollectionview.reloadData()
cell.attCollHeight.constant = cell.attetchmentsCollectionview.collectionViewLayout.collectionViewContentSize.height;

EDIT:

Left aligned cell and 1 item per row, add this delegate method:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout

return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: collectionView.frame.width - flowLayout.itemSize.width)
}

Dynamic UICollectionView Cell Height

You could set the Estimated Size of the collectionView as "Automatic" as well as the cell's size and each cell will self size with Auto Layout constrained views.

As stated in the Xcode 11 Release Notes:

Cells in a UICollectionView can now self size with Auto Layout
constrained views in the canvas. To opt into the behavior for existing
collection views, enable “Automatic” for the collection view’s
estimated size, and “Automatic” for cell’s size from the Size
inspector. If deploying before iOS 13, you can activate self sizing
collection view cells by calling performBatchUpdates(_:completion:)
during viewDidLoad(). (45617083)

How to setup a UICollectionView with dynamic height and dynamic cell widths programmatically?

Here you'll need to create a custom collectionViewLayout by creating a new class that is extending UICollectionViewFlowLayout and in that class you'll need to override the layoutAttributesForElements(in rect: CGRect) method.
In that overridden method, what you'll be doing is basically that you'll be calculating the position for each cell again by modifying the UICollectionViewLayoutAttributes of each cell, and then returning that new modified UICollectionViewLayoutAttributes for each cell.

class LeftAlignedCellsCustomFlowLayout:UICollectionViewFlowLayout {

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

//get an array of UICollectionViewLayoutAttributes for all the cells
let attributes = super.layoutAttributesForElements(in: rect)

var leftMargin = sectionInset.left

var maxY: CGFloat = 2.0

let horizontalSpacing:CGFloat = 5

//Modify the UICollectionViewLayoutAttributes for each cell
attributes?.forEach { layoutAttribute in

if layoutAttribute.frame.origin.y >= maxY
|| layoutAttribute.frame.origin.x == sectionInset.left {

leftMargin = sectionInset.left
}

if layoutAttribute.frame.origin.x == sectionInset.left {
leftMargin = sectionInset.left
}else {
layoutAttribute.frame.origin.x = leftMargin
}

leftMargin += layoutAttribute.frame.width + horizontalSpacing

maxY = max(layoutAttribute.frame.maxY, maxY)
}
//return the array of modified UICollectionViewLayoutAttributes
return attributes
}
}

Then in your View where you want to put this custom aligned CollectionView do the following:

  //Let's say you have an IBOutlet of your collectionView as a class-level instance
@IBOutlet weak var leftAlignedCollectionView: UICollectionView!

//put the following code in the awakeFromNib of your View:
let layout = LeftAlignedCellsCustomFlowLayout()
layout.estimatedItemSize = CGSize(width: 1, height: 1)
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
leftAlignedCollectionView.collectionViewLayout = layout

Now, all you have to do is make a collectionViewCell which has a label inside it having left,right, top, bottom spacing. And, Inside the cell class put the following code:

var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {

//We need to cache our calculation to prevent a crash.

if !isHeightCalculated {

layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.width = CGFloat(ceilf(Float(size.width)))
layoutAttributes.frame = newFrame
isHeightCalculated = true

}
return layoutAttributes
}

The overriding of the above method Gives the cell a chance to modify the attributes provided by the layout object.

UICollectionView Dynamic Cell Height as per the content | Pinterest Layouts

What you have mentioned can be achieved using flow layout — a layout class provided by UIKit.

This is what you are looking for:

https://www.raywenderlich.com/392-uicollectionview-custom-layout-tutorial-pinterest

I hope you can put some effort into reading this very simple post and follow each step carefully.



Related Topics



Leave a reply



Submit