I've got a collection view that's set up as a single horizontal row of cells. It's throwing the following constraint error (I'm using AutoLayout):
The behavior of the UICollectionViewFlowLayout is not defined because the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I've Googled around and looked on SO, and everyone suggests that it's fixed by simply overriding the UIScrollViewDelegate methods:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
// iPhone 6+ device width handler
CGFloat multiplier = (screenWidth > kNumberOfCellsWidthThreshold) ? 4 : 3;
CGFloat size = screenWidth / multiplier;
return CGSizeMake(size, size);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
But it's still not working. It appears to be gawking at the fact that the cell height is taller than it's container, even though I'm setting the cell to the same hight as the container. Here's the rest of the error:
The relevant UICollectionViewFlowLayout instance is UICollectionViewFlowLayout: 0x7fa6943e7760, and it is attached to MyCollectionView: 0x7fa69581f200; baseClass = UICollectionView;
frame = (0 0; 375 98); clipsToBounds = YES; autoresize = RM+BM;
layer = CALayer: 0x7fa694315a60; contentOffset: {0, 0}; contentSize: {0, 134}
Setting the cell manually to a drastically smaller size (e.g. size - 50.0 seems to work, but why can't I set the cell size to the same height as its container?
Turns out the missing piece from my question was the fact that this UICollectionView is nested inside a UITableView. iOS 8 introduced layoutMargins in place of content offset values.
Per this question: iOS 8 UITableView separator inset 0 not working
I had to override my containing table view's cell margins:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// Remove seperator inset
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
// Prevent the cell from inheriting the Table View's margin settings
if ([cell respondsToSelector:#selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
// Explictly set your cell's layout margins
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
swift for override cell
override func awakeFromNib() {
super.awakeFromNib()
self.separatorInset = .zero
self.preservesSuperviewLayoutMargins = false
self.layoutMargins = .zero
}
Related
I've got some strange problem related to dynamically sized cells, auto layout and size classes. My test project is completely based on Ray's tutorial. It seems that the only difference is UILabels's font sizes for size classes.
The height calculation is wrong when I set another font size for label in some size class. I've made screenshot to illustrate it.
Wwith wrong cell's height calculations:
With correct cell's height calculations:
In addition, I've pushed test project to the github.
EDITED:
ViewController.m
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForCellAtIndexPath:indexPath];
}
- (CGFloat)heightForCellAtIndexPath:(NSIndexPath *)indexPath {
static CustomTableViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:kBasicCell];
});
[self configurateCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForCell:sizingCell];
}
- (CGFloat)calculateHeightForCell:(CustomTableViewCell *)cell {
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.frame), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height +1.0f;
}
CustomLabel.m
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[self setNeedsUpdateConstraints];
}
Since you're already using Auto Layout, I'd recommend also taking advantage of self-sizing cells. You won't need any row height calculation, as iOS can adjust the cell's height automatically, based on the UILabel height.
Add Auto Layout constraints to your contentView's UILabel. This will cause the cell to adjust its contentView height based on the label's contents.
Enable row height estimation.
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
I have a problem with iOS UICollectionView.
I have a UICollectionView with a layout:
UICollectionViewFlowLayout *collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
collectionViewFlowLayout.itemSize = CGSizeMake(COLLECTION_VIEW_CELL_WIDTH, COLLECTION_VIEW_CELL_HEIGHT);
collectionViewFlowLayout.minimumLineSpacing = 3.0f;
My UICollectionView frame is frame = (0 ,45, 769,307);
Also, I have a custom UICollectionViewCell with frame frame = (0 ,0, 190, 100)
190 * 4 = 760. That means, that 4 cells should fit into my collection view width. But there is only 3 of them...
Where is my problem?
you can try this delegate
#pragma mark collection view cell paddings
- (UIEdgeInsets)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0); // top, left, bottom, right
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 0.0;
}
I make custom cell with 2 multiline labels and pin this labels to all sides. When in -tableView:heightForRowAtIndexPath: for iOS >= 8 I return UITableViewAutomaticDimension. But when table view appears the cell height is bigger than should be. After I scroll down and then scroll up the cell takes the normal height. How to fix this behavior?
Code for height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
return UITableViewAutomaticDimension;
}
static CCTopicTableViewCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:#"topicCellIdentifier"];
});
cell.topic = [self.topics topicAtIndexPath:indexPath];
cell.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1;
}
And the result:
After scrolling:
The problem is gone when I don't set tableView's estimatedRowHeight for iOS 8 and update preferredMaxLayoutWidth every time when sets the text for multiline labels
I solved this issue by setting "text" property of multiline labels to "nil":
override func prepareForReuse() {
_label.text = nil
}
Also table properies "estimatedRowHeight" and "rowHeight" should be set correctly:
_tableView.estimatedRowHeight = 44
_tableView.rowHeight = UITableViewAutomaticDimension
Automatic dimension is not working well for some UILabel if that label is aligned with different label by top edges or bottom edges. Just make sure that you do not have such alignments.
Set automatic dimension in viewDidLoad() method or viewWillAppear() (both code in same place)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 160.0;//(Maximum Height that should be assigned to your cell)
Visit Using Auto Layout in UITableView for dynamic cell layouts & variable row heights an intuitive guide for both iOS 7 and iOS 8
I'm currently working on autosizing cells which are new in iOS8.
According to WWDC 2014 video 'What's New in Table and Collection Views', all that is required to autosize cells is to set an estimatedSize on the UICollectionViewFlowLayout given to the UICollectionView on initialisation, and override the sizeThatFits: method in the UICollectionViewCell.
Initialisation:
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setEstimatedItemSize:CGSizeMake(146.0f, 156.0f)];
_collectionView = [[UICollectionView alloc] initWithFrame:[_containerView bounds] collectionViewLayout:flowLayout];
sizeThatFits method in my cell, where _userLabel is the label that is on the bottom and marks the actual height.
- (CGSize)sizeThatFits:(CGSize)size
{
CGSize actualSize = CGSizeMake([[self contentView] bounds].size.width, [_userLabel frame].origin.x + [_userLabel bounds].size.height);
return actualSize;
}
It turns out that actual size has larger height than my estimated size, which should be okay and the cell actually gets resized properly in height.
Now with the iPhone6 and 6+, the width can also differ. I want the cells to properly span the width of the screen. I've therefore implemented three delegate methods on UICollectionViewFlowLayout; my first intuition is to calculate the width I want the cells to have according to the screen width, the section insets and the inter-item spacing.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIEdgeInsets sectionInsets = [self collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:[indexPath section]];
CGFloat interItemSpacing = [(UICollectionViewFlowLayout *) collectionViewLayout minimumInteritemSpacing];
CGFloat cellWidth = ([_collectionView bounds].size.width - sectionInsets.left - sectionInsets.right - interItemSpacing) / 2;
CGSize collectionViewCellSize = CGSizeMake(cellWidth, 156.0f);
return collectionViewCellSize;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 12.0f;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(10.0f, 10.0f, 10.0f, 10.0f);
}
It works, however partly. The collectionView won't respect the top section inset, and when scrolling the collectionView, switching out another tab and then back again, cells seem to have disappeared.
Apparently I'm doing something wrong, but I can't seem to figure out where. Anyone got a clue?
I have a UICollectionView which uses a waterfall style layout to show different sized cells. The height calculation for each cell works just fine.
The problem I have is that I have to change the size of two outlets (UIImage and UILabel) in each cell and I donĀ“t know where to change the frame size in order prevent the UICollectionView from animating said frame size change. I tried it to do in the layoutSubviews method of each cell and in the cellForItemAtIndexPath without results. Every time the viewcontroller containing the UICollectionView appears the frame change for the two outlets in each cell is animated.
Example setting the frame for just the image inside cellForItemAtIndexPath:
- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CVCNewspaper *cell = (CVCNewspaper *)[collectionView dequeueReusableCellWithReuseIdentifier:CELL_IDENTIFIER_NEWSPAPER forIndexPath:indexPath];
BONewspaper *newspaper = [newspapers objectAtIndex:indexPath.row];
CGFloat width = cell.frame.size.width;
// Calculate rect for imageView
CGFloat objectWidth = newspaper.coverNews.imageWidth ? newspaper.coverNews.imageWidth : 180;
CGFloat objectHeight = newspaper.coverNews.imageHeight ? newspaper.coverNews.imageHeight : 100;
CGFloat scaledHeight = floorf(objectHeight / (objectWidth / width));
cell.imageViewCover.frame = CGRectMake(MARGINVIEW, HEADERHEIGHT + 5, width, scaledHeight);
[cell fillCellWithNewspaper:newspaper];
return cell;
}