Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto-Layout: Get UIImageView height to calculate cell height correctly

So far I have been just using text labels within cells that should auto-size themselves. I usually put constraints to all of the edges (content view or neighbors) and add the following lines to my viewDidLoad():

// 190.0 is the actual height of my custom cell (see next image) in the storyboard containing a label and a randomly sized UIImageView
tableView.estimatedRowHeight = 190.0
tableView.rowHeight = UITableViewAutomaticDimension

Now, when trying to include with images, I face several problems. During some research I found several approaches written in Objective-C but whereas I'm not sure which of those really addressed my central problem I'm still struggling with complex Objective-C code. So to resume: My final goal is to get a table with correctly sized (correct heights) cells, each containing a title and an image that fits the width of the screen. Those images can be of different aspect ratios and different sizes. To simplify some of the challenges, I prepared a new, more simple project:

  1. First, I created a UITableViewController and a CustomCell class with two IBOutlets title: String and postedImage: UIImage (UIImage randomly sized in the storyboard) to configure the cells. I defined constraints to the edges and to each other.

  1. When built, two cells get configured containing the same image but they are fetched from two individual files with different sizes and resolutions (900 x 171 vs. half-sized 450 x 86). The UIImage view mode is set to "Aspect Fit" but as I couldn't get it run the way I want so far I'm not sure this is the right setting. Whereas I had expected the cell height to be calculated based on the rescaled UIImage inclusively the constraints I had set, it seems to be based on the initial height of the image files (so 171 and 86). It doesn't actually adapt to the actual UIImage view height (around 70 I'd guess).

Both cell heights are based on the initial image file height and not on the actual UIImage height

In a somewhat more complex project of mine I want different images to automatically fit the width of the screen no matter whether they are portrait or landscape - and in case their width is less then the screen's width, they should be scaled up. Like in the project above each cell height should fit its individual content as defined by the constraints in the storyboard. Anyway, it starts with the problem discussed above: How can I let those two cells have the same, correct height, hugging its content like defined in the storyboard?

Thanks a million for any help!

like image 301
Alexei S. Avatar asked Sep 25 '14 15:09

Alexei S.


3 Answers

So I think the underlying issue is a kind of chicken and egg problem.

In order to 'aspect fit' the image to the UIImageView the system needs a size for the view, and yet we would like the height of the view to be determined after aspect fitting the image given a known width for the view.

A workaround is to calculate the aspect ratio of the image ourselves and set it as a constraint on the UIImageView when we set the image. That way the auto layout system has enough information to size the view (a width and an aspect ratio).

So I changed your custom cell class to:

class CustomCell: UITableViewCell {

    @IBOutlet weak var imageTitle: UILabel!

    @IBOutlet weak var postedImageView: UIImageView!

    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                postedImageView.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                postedImageView.addConstraint(aspectConstraint!)
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        aspectConstraint = nil
    }

    func setPostedImage(image : UIImage) {

        let aspect = image.size.width / image.size.height

        aspectConstraint = NSLayoutConstraint(item: postedImageView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: postedImageView, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)

        postedImageView.image = image
    }

}

And then in the delegate do this instead of setting the image directly:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell

        cell.imageTitle.text = titles[indexPath.row]

        let image = images[titles[indexPath.row]]!
        cell.setPostedImage(image)

        return cell
}

And you will get:

enter image description here

Hope this helps!

like image 63
Mike Pollard Avatar answered Nov 11 '22 06:11

Mike Pollard


I translated Pollard's solution to objc version something like this.
Also I got warnings like "Unable to simultaneously satisfy constraints". My solution is set the priority of vertical space constraint as 999 rather than 1000.

@interface PostImageViewCell()

@property (nonatomic, strong) NSLayoutConstraint *aspectContraint;
@end

@implementation PostImageViewCell

- (void) setAspectContraint:(NSLayoutConstraint *)aspectContraint {

    if (_aspectContraint != nil) {
        [self.postImage removeConstraint:_aspectContraint];
    }
    if (aspectContraint != nil) {
        [self.postImage addConstraint:aspectContraint];
    }
}

- (void) prepareForReuse {
    [super prepareForReuse];
    self.aspectContraint = nil;
}

- (void)setPicture:(UIImage *)image {

    CGFloat aspect = image.size.width / image.size.height;
    self.aspectContraint = [NSLayoutConstraint constraintWithItem:self.postImage
                                                        attribute:NSLayoutAttributeWidth
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self.postImage
                                                        attribute:NSLayoutAttributeHeight
                                                       multiplier:aspect
                                                         constant:0.0];
    [self.postImage setImage:image];
}
@end
like image 23
허광호 Avatar answered Nov 11 '22 07:11

허광호


Working in Swift 3 + iOS 8

Using AutoLayout and variable sized UITableViewCell objects -

For imageview, set top, bottom, leading and trailing space constraints to 0.

Code to be added to viewDidLoad()

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200

Define UITableViewDelegate's heightForRowAtIndexPath ()

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let image = UIImage(named: list_images[indexPath.row])
        let aspect = (image?.size.width)! / (image?.size.height)!
        let cellHeight = (tableView.frame.width / aspect) + <<Height of image title label>>
        return cellHeight
    }
}
like image 1
c_sharma Avatar answered Nov 11 '22 05:11

c_sharma