I have a UIStackView
contained inside of a UICollectionViewCell
to represent a post on a social network (similar style to Instagram's cells). The UIStackView
contains an author header (custom UIView
), a UIImageView
for the content image, a UILabel
for the post body, and a UIToolbar
for the actions. The final view looks like this.
The height of the UICollectionViewCell is being set by setting up a sizing cell, like so:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NTVFeedBlogCollectionViewCell *cell = [[NTVFeedBlogCollectionViewCell alloc] initWithFrame:CGRectMake(10.0f, 0.0f, collectionView.frame.size.width - 20.0f, 900000.0)];
// ...
// Configure the cell
// ...
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return CGSizeMake(CGRectGetWidth(collectionView.frame) - 20.0f, size.height);
}
The problem is that the UIImageView appears to be increasing the size of the UICollectionViewCell, without increasing the size of the image view. This is the result when I add a large image to the image view. The desired result is for the image to remain a 1:1 aspect ratio, constrained to the width of the UIStackView.
The size of the gap between the body label and the rest of the content changes depending on which image I use. This leads me to believe that UIStackView
is somehow taking the image size into consideration for the sizing of the cell only, because visually the image view is its correct size. As you saw in the first image posted, the cell lays out perfectly when the image view's image has not been set.
Here is the setup code for the stack view, image view and labels:
self.stackView = [[UIStackView alloc] initWithFrame:CGRectZero];
self.stackView.translatesAutoresizingMaskIntoConstraints = NO;
self.stackView.axis = UILayoutConstraintAxisVertical;
self.stackView.distribution = UIStackViewDistributionFill;
self.stackView.alignment = UIStackViewAlignmentFill;
self.stackView.spacing = 10.0f;
self.stackView.layoutMargins = UIEdgeInsetsMake(10.0f, 10.0f, 0.0f, 10.0f);
self.stackView.layoutMarginsRelativeArrangement = YES;
[self addSubview:self.stackView];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[stackView]-0-|"
options:0
metrics:nil
views:@{@"stackView": self.stackView}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[stackView]-0-|"
options:0
metrics:nil
views:@{@"stackView": self.stackView}]];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
[self.imageView setImage:[UIImage imageNamed:@"sample_image.png"]];
self.imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.clipsToBounds = YES;
self.imageView.layer.masksToBounds = YES;
self.imageView.backgroundColor = [UIColor greenColor];
[self.imageView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
[self.imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisVertical];
[self.stackView addArrangedSubview:self.imageView];
[self.stackView addConstraint:[NSLayoutConstraint constraintWithItem:self.imageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.stackView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
self.bodyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.bodyLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
[self.bodyLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisVertical];
self.bodyLabel.font = [UIFont ntv_lightFontWithSize:17.0f];
self.bodyLabel.textColor = [UIColor whiteColor];
self.bodyLabel.numberOfLines = 0;
self.bodyLabel.text = @"While the stack view allows you to layout its contents without using Auto Layout directly, you still need to use Auto Layout to position the stack view, itself.";
[self.stackView addArrangedSubview:self.bodyLabel];
self.accessoryView = [[NTVAccessoryView alloc] initWithFrame:CGRectZero];
self.accessoryView.viewModel = [NTVAccessoryManagerViewModel_Stubbed new];
[self.stackView addArrangedSubview:self.accessoryView];
Any ideas?
We figured it out! As stated in the docs, the UIStackView
is using the intrinsicContentSize
of its arrangedSubviews
to calculate its height.
UIImageView
is reporting its intrinsicContentSize
as the whole size of the image (as you would expect). UIStackView
was ignoring my height constraint and just using the intrinsicContentSize
of the image view.
To fix this, I've created a UIImageView
subclass which allows for a caller to set the intrinsicContentSize
. This code can be found here.
Then, in my UICollectionViewCell
I am setting the UIImageView subclass' intrinsicContentSize
once the layout pass has completed:
- (void)layoutSubviews {
[super layoutSubviews];
// Constrain the image view's content size to match the stackview's width.
self.imageView.constrainedSize = CGSizeMake(self.stackView.frame.size.width, self.stackView.frame.size.width);
[super layoutSubviews];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With