I have a problem resizing the cells of a collection view to fit their content. I have a method that works fine, but with iOS 14 this method fails and no longer works. The code I am using is:
In the layout (subclass of UICollectionViewFlowLayout:
- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
withYOffset:(CGFloat)yOffset {
CGFloat x = 0.0;
CGFloat y = yOffset;
CGFloat width = self.collectionView.bounds.size.width - self.collectionView.contentInset.left - self.collectionView.contentInset.right;
CGFloat height = [self.delegate heightForItemAtIndexPath:indexPath];
return CGRectMake(x, y, width, height);
}
In the view controller:
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCollectionViewCell *cell = [self.dataSource sizingCellAtIndexPath:indexPath];
[cell setFrame:CGRectMake(cell.bounds.origin.x, cell.bounds.origin.y, UIScreen.mainScreen.bounds.size.width, cell.bounds.size.height)];
CGSize size = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
withHorizontalFittingPriority:UILayoutPriorityRequired
verticalFittingPriority:UILayoutPriorityDefaultLow];
return size.height;
}
As I said, this works fine until iOS 13.x but with iOS 14 fails and breaks the constraints. I have been searching a lot, and in stackoverflow there are a lot of answers but nothing seems to work for this.
Does someone know why this is not working on iOS 14 and how fix this?
Thanks in advance.
UPDATE
The method sizingCellAtIndexPath called in the first line of the method in the controller is:
- (MyCollectionViewCell *)sizingCellAtIndexPath:(NSIndexPath *)indexPath {
Article *article = [self articleAtIndexPath:indexPath];
NSString *cellIdentifier = [self cellIdentifierForCellAtIndexPath:indexPath];
MyCollectionViewCell *sizingCell = [MyCollectionViewCell sizingCellWithNibName:cellIdentifier];
[sizingCell configureWithArticle:article sizing:YES cellIdentifier:cellIdentifier indexPath:(NSIndexPath *)indexPath];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
return sizingCell;
}
Related
I am stuck in here trying to achieve a dynamic cell height for my collectionView.
So here is my case:
I have a UICollectionViewCell xib file which contains the following controls with their constraints to achieve the dynamic cell height :
UIButton
UILabel (Fixed Height)
UILabel (Fixed Height)
UILabel (Dynamic Height)
as the image below :
Everything was working fine when the UIButton had a fixed height (130) with the dynamic text for the last UILabel as Image #1, until I changed the constraint for the UIButton as follow:
Remove the height constraint (height = 130).
add aspect ratio (1:1).
As Image #2 shown:
Image #1
Image #2
For the code side here is what I did:
1-UICollectionViewCell subclass:
-(void)awakeFromNib {
[super awakeFromNib];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
}
-(void)layoutSubviews {
[self layoutIfNeeded];
}
-(CGSize)preferredLayoutSizeFittingSize:(CGSize)targetSize
{
CGRect originalFrame = self.frame;
CGRect frame = self.frame;
frame.size = targetSize;
self.frame = frame;
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize computedSize = [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGSize newSize = CGSizeMake(targetSize.width, computedSize.height);
self.frame = originalFrame;
return newSize;
}
and inside my ViewController which implements the UICollectionView delegate:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.arrData.count;
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
BEBuyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.reuseID forIndexPath:indexPath];
if([cell isKindOfClass:NSClassFromString(#"BEReceiverCollectionViewCell")])
{
cell.isReceiver = YES;
cell.iType = indexPath.item+1;
}
cell.object = self.arrData[indexPath.row];
[cell fillData];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat width = [(BECollectionViewFlowLayout*)collectionViewLayout itemWidth];
CGSize targetSize = CGSizeMake(width, 0);
if(sizingCell == nil)
{
UINib *nibFile = [UINib nibWithNibName:#"CustomCell" bundle:nil];
sizingCell = [[nibFile instantiateWithOwner:self options:nil] firstObject];
}
sizingCell.object = self.arrData[indexPath.row];
[sizingCell fillData];
CGSize adequateSize = [sizingCell preferredLayoutSizeFittingSize:targetSize];
CGSize size = CGSizeMake(width, adequateSize.height + iOffset*2);
return size;
}
Note: BECollectionViewFlowLayout is a custom UICollectionViewFlowLayout to make my UICollectionView show 2 columns by customising the layout of the UICollectionView
So what I am missing to achieve the correct dynamic height for my collectionView?
I am creating an application with iOS 7 devices. This application contains UITableView with dynamic cell height.
To create this I have a custom cell which contains methods:
- (CGFloat)heightWithModel:(Model *)model
{
CGFloat height = 0.0f;
height = self.contentLabel.frame.origin.y + [self contentHeightWithModel:model] + CellBottomOffset;
return height;
}
and...
- (CGFloat)contentHeightWithModel:(Model *)model
{
CGFloat height = 0.0;
NSDictionary *attributes = [NSDictionary dictionaryWithObject:[UIFont systemFontOfSize:14.0f] forKey:NSFontAttributeName];
NSString *string = model.content;
NSStringDrawingContext *context = nil;
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin;
CGSize size = CGSizeMake(self.contentLabel.bounds.size.width, CGFLOAT_MAX);
CGRect frame = [string boundingRectWithSize:size options:options attributes:attributes context:context];
height = frame.size.height;
return height;
}
In my view controller, I have implemented UITableViewDelegate protocol method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat height;
static Cell *cell;
if (!cell)
{
cell = [tableView dequeueReusableCellWithIdentifier:CellReuseIdentifier];
}
height = [cell heightWithModel:[self.dataSource.models objectAtIndex:indexPath.row]];
return height;
}
As far as I understand, this should be enough to create a table view with dynamic cell height. Despite this, I have a table view like this:
As you see, part of text are hidden, because label (red one) height is too small. Cell height is set dynamically by using Auto Layout (10 px from the bottom).
Can anyone see where is the problem?
First take a variable
CGFloat height;
put this lines in viewdidload method
in that set estimated height of cell and for that label set lines to 0 in inspector panel and change its height = to >= and give constraint from all side
[self.tbl_rating layoutIfNeeded];
[self.tbl_rating setNeedsLayout];
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1 )
{
self.tbl_rating.estimatedRowHeight=153.0f;
self.tbl_rating.rowHeight=UITableViewAutomaticDimension;
}
Now put this two method for finding label height and dynamic cell height increment
In this method set your label text that you retriving from like array or web service
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// 7.1>
if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 ) {
height=[self findHeightForText:[NSString stringWithFormat:#"%#",[[arr_review valueForKey:#"desc"]objectAtIndex:indexPath.row]] havingWidth:self.view.frame.size.width andFont:[UIFont systemFontOfSize:12.0f]].height;
return 153+height;
}
return UITableViewAutomaticDimension;
}
-(CGSize)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGSize size = CGSizeZero;
if (text) {
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:font } context:nil];
size = CGSizeMake(frame.size.width, ceil(frame.size.height));
}
return size;
}
NOTE: In tableview method heightForRowAtIndexPath I recommand you pass text from array value. If this thing doesnt work then check your constraints.
This thing works for me Hope it will work.
Thank you.
Since I have been struggling for 3 days with this problem and have asked about it twice already, but maybe was not clear, I had decided to investigate the issue & found a buggy behavior with this view.
I will show the entire simple code, so anyone can reproduce the bug (iPad Air).
I am setting a collectionView flowlayout that subclasses the layout to get a constant spacing between cells, and here is the start:
TopAlignedCollectionViewFlowLayout *layout = [[TopAlignedCollectionViewFlowLayout alloc] init];
CGRect size = CGRectMake(0, 0, 900, 1200);
self.GridView = [[UICollectionView alloc] initWithFrame:size
collectionViewLayout:layout];
[self.GridView registerClass:[GridCell class] forCellWithReuseIdentifier:#"Cell"];
[self.GridView setDelegate:self];
[self.GridView setDataSource:self];
[self.view addSubview:self.GridView];
Then setting my delegates is as simple as that : (height is dynamic )
#pragma grid- main functions
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 80;
}
//cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
//a random dynamic height of a cell
int a = arc4random()%300;
CGSize size = CGSizeMake( 340, 240+a );
return size;
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
GridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier
forIndexPath:indexPath];
cell.textL.text=[NSString stringWithFormat:#"%d",indexPath.row];
NSLog(#"%d",indexPath.row);
return cell;
}
Now the subclass, to get a constant spacing : (TopAlignedCollectionViewFlowLayout)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
#define numColumns 2
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item < numColumns) {
CGRect f = currentItemAttributes.frame;
f.origin.y = 0;
currentItemAttributes.frame = f;
return currentItemAttributes;
}
NSIndexPath* ipPrev = [NSIndexPath indexPathForItem:indexPath.item-numColumns
inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat YPointNew = fPrev.origin.y + fPrev.size.height + 10;
CGRect f = currentItemAttributes.frame;
f.origin.y = YPointNew;
currentItemAttributes.frame = f;
return currentItemAttributes;
}
Anyone can check and see that after you scroll for a while, you get a strange effect of blank spaces that are filled lately by their cells,something like :
1 2
3 4
6
8
NOTE: 5-7 are loaded in later.
EDIT1:
Removing the random height from the cell size delegate method, set it to be constant height, solves this issue.
Problem is: Cell's height must be dynamic.
EDIT2:
Setting the random height (int a) to be smaller, makes also the problem to disappear,(<100), means that the smaller the distance height between cells, more likely the problem will not occur .
EDIT3 !
I have managed to set a constant distance between cells, not with subclass of the layout, but with my own memory by saving the previous cell origin and height, so i have got the constant spacing but the problem is back again ! seems that if the cells are in some certain structure, it makes the callback method that create cells, to not being called in time ! wow , i am really wondering how no one had seen this before ..
here is my implementation to create spacing with no subclassing,that also cause the problem:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
GridCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.textL.text=[NSString stringWithFormat:#"%ld",(long)indexPath.row];
NSLog(#"%d",indexPath.row);
if(indexPath.row>1)
{
NSIndexPath* ipPrev = [NSIndexPath indexPathForItem:indexPath.item-2 inSection:indexPath.section];
float prey=[[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"y:%ld",(long)ipPrev.row]] floatValue];
float preh=[[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"h:%ld",(long)ipPrev.row]] floatValue];
cell.frame=CGRectMake(cell.frame.origin.x, preh+prey+10, cell.frame.size.width, cell.frame.size.height);
[[NSUserDefaults standardUserDefaults] setFloat:cell.frame.origin.y forKey:[NSString stringWithFormat:#"y:%ld",(long)indexPath.row]];
[[NSUserDefaults standardUserDefaults] setFloat:cell.frame.size.height forKey:[NSString stringWithFormat:#"h:%ld",(long)indexPath.row]];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"this index:%d",indexPath.row);
NSLog(#"this cell y:%f",cell.frame.origin.y);
NSLog(#"this cell height:%f",cell.frame.size.height);
NSLog(#"previous index:%ld",(long)ipPrev.row);
NSLog(#"previous cell y: %#",[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"y:%ld",(long)ipPrev.row]]);
NSLog(#"previous cell height: %#",[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"h:%ld",(long)ipPrev.row]]);
NSLog(#"------------------------");
}
return cell;
}
Seems its a serious bug in UICollectionView, when images are bigger than screen size.
1 . UICollectionView fall back to UIScrollView
2.Large UICollectionViewCell's disappearing with custom layout
3.http://stripysock.com.au/blog/2013/3/14/working-around-a-bug-in-uicollectionview
So much work and time, for this stupid bug of apple with the reusable cells that causes nothing but headache with so many strange behaviours.
For developers who didn't have the problem i can say- just try to set dynamic height images, and iPad simulator, and you will see bugs that are just unbelievable.
So no answer for me, i will have to implement the whole thing by my self with a scrollview ,since i dont want to be depended again on things such PSTCollectionView
I have a chat function in the app I am making. Each cell has a UITextView and I need to calculate the **height and width** of each cell. I do that and then cache that data.
Using NSLog I have determined that the data is correctly cached and the height for each row is only being asked for once.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
NSString *reuseIdentifier;
if (message.iSentThisMessage) {
reuseIdentifier = #"MeCell";
} else {
reuseIdentifier = #"HostCell";
}
ChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textView.text = message.content;
if (message.frame.size.height == 0 && message.frame.size.width == 0) {
CGRect rect = cell.textView.frame;
rect.origin.x = 5;
rect.origin.y = 3;
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
rect.size.height = size.height
+ cell.textView.textContainerInset.bottom
+ cell.textView.textContainerInset.top;
rect.size.width = [cell.textView sizeThatFits:CGSizeMake(225.0, rect.size.height)].width
+ cell.textView.textContainerInset.left
+ cell.textView.textContainerInset.right;
if (message.iSentThisMessage) {
rect.origin.x = 320 - (rect.origin.y + rect.size.width);
}
cell.textView.frame = rect;
message.frame = rect;
NSLog(#"Not Cached :(");
} else {
cell.textView.frame = message.frame;
NSLog(#"Cached!");
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
if (indexPath.row + 1 == feedItems.count)
size.height += 4;
NSLog(#"Height asked for.");
return size.height + 12;
}
Also, one other possibility may be that I am overriding the following method in my custom uitableviewcell class because otherwise it rearranges the contents of my cell.
- (void) layoutSublayersOfLayer:(CALayer *)layer {
}
I can't seem to figure out how to make it not lag!!
Not clear why you're using a text view, but you should change to a label if you can.
Also, don't cache the height in your table delegate method, that's a nasty side effect. Calculate the height up front, either when the message is loaded / received, or on a background thread for batch load.
The text view / label frame should also be relative to the cell, either by auto resizing / layout. So you should never set it explicitly.
On top of that - profile using instruments. This is the only thing that will really tell you what's wrong.
This is my story:
I have a problem with the size of my IUTableViewCell. When I add several cell, the cell auto resizing.
any answer will be appreciated :)
That my code to resize:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPathInCellTable:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell*) [self tableView:tableView
cellForRowAtIndexPath:indexPath];
CGSize size;
// SIZE HEIGHT TEXT
size = [cell.color.text sizeWithAttributes:
#{NSFontAttributeName:
[UIFont systemFontOfSize:12.0f]}];
// SIZE HEIGHT FOR CELL
CGRect frame = [cell frame];
frame.size.height += size.height;
[cell setFrame:frame];
// SIZE HEIGHT IMG
CGRect frame = [cell.img frame];
frame.size.height = 69;
frame.size.width = 69;
[cell.img setFrame:frame];
if (indexPath.row == 0) [self setHeightTableView:0];
_tableHeightConstraint.constant += cell.frame.size.height;
return cell.frame.size.height;
}
There some screenshot :
the first time i add a cell everything is fine
the same for the second cell everything is fine
And there the problem comes
You call CustomCell
*cell = (CustomCell*) [self tableView:tableView
cellForRowAtIndexPath:indexPath];
to get the a cell, which is wrong, because there is no cell created yet (so its nil). tableView:heightForCell:atIndexPath: get called before the cell was created. The best solution would be to have a module abject to save the height needed for your cell or make some similar calculations
Use a prototype cell to get the sizing instead of using tableView:cellForRowAtIndexPath:
http://www.samrayner.com/posts/dynamic-tableview-cells/