Alexei S. Alexei S. - 2 months ago 17
Swift Question

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!

Answer

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!