Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIStackView miscalculating UIImageView height

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?

like image 304
Kiran Panesar Avatar asked Oct 27 '15 13:10

Kiran Panesar


1 Answers

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];

}

like image 159
Kiran Panesar Avatar answered Oct 28 '22 00:10

Kiran Panesar