Joseph Beuys' Mum
Joseph Beuys' Mum

Reputation: 2264

How to set constraints for resizable heights on UITableViewCells that have between 0 and 2 images?

Code follows after this explanation of the problem.

I have a UIViewController containing a UITableView, the UITableViewCell's of which show either one, two, or zero images. With one and two images, the height of the cell is always 72, but without any images it needs to be zero. I have two UIView's in my table cell, the first of which contains one UIImageView and the second of which contains two of them. I'm either hiding or showing the relevant one depending on whether that cell has one or two images. If the cell has zero images, both UIView's are hidden, but of course they still form part of the constraints set up and so the resulting cell is still 72 pixels tall, whereas I need it to collapse.

I have been told that the only way to create height-resizable UITableViewCell's is by setting constraints in the storyboard, and that messing around with the values of constraints programmatically on-the-fly will cause layout problems. I do not know whether any of that advice is true or not, so I have tried the following (with no success):

  1. setting explicit references to my UIView's NSLayoutConstraint's that control height in my ResizableImageCell call, which cause Unable to simultaneously satisfy constraints errors; and

  2. setting explicit references to all NSLayoutConstraint's that control height in my ResizableImageCell call; and/or

  3. adding and removing UIView's, both of which work fine until you start scrolling through the table, whereupon the table starts jumping up and down after you lift your finger from the screen.

This is the current code that controls the ViewController:

import UIKit

class ViewController: UIViewController
{
    @IBOutlet weak var tableView: UITableView!

    let imageNames = [ "room", "street", "tree" ]



    override func viewDidLoad ()
    {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 44.0
    }



    func getRandomInt ( from start: Int, to end: Int ) -> Int
    {
        return Int( arc4random_uniform( UInt32( ( end + 1 ) - start ) ) ) + start
    }
}



extension ViewController: UITableViewDataSource
{
    @available( iOS 2.0, * )
    public func tableView ( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell( withIdentifier: "ResizableImageCell" ) as! ResizableImageCell
        let numImages = getRandomInt( from: 0, to: 2 )

        cell.view1.isHidden = numImages != 1
        cell.view2.isHidden = numImages != 2

        var rand = getRandomInt( from: 0, to: imageNames.count - 1 )

        switch numImages
        {
        case 1:     cell.image1.image = UIImage( named: imageNames[ rand ] )
        case 2:     cell.image2.image = UIImage( named: imageNames[ rand ] )
        default:    ()
        }

        guard numImages == 2 else { return cell }

        rand = getRandomInt( from: 0, to: imageNames.count - 1 )

        cell.image3.image = UIImage( named: imageNames[ rand ] )

        return cell
    }



    func tableView ( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int
    {
        return 64
    }
}

This is the current code that controls my bespoke ResizableImageCell cell

import UIKit

class ResizableImageCell: UITableViewCell
{
    @IBOutlet weak var view1: UIView!
    @IBOutlet weak var view2: UIView!

    @IBOutlet weak var image1: UIImageView!
    @IBOutlet weak var image2: UIImageView!
    @IBOutlet weak var image3: UIImageView!



    override func awakeFromNib ()
    {
        super.awakeFromNib()
    }
}

And here is a screenshot of the existing storyboard:

screenshot of a storyboard from XCode

Thank you for reading.

Upvotes: 1

Views: 95

Answers (2)

Joseph Beuys' Mum
Joseph Beuys' Mum

Reputation: 2264

Having solved this problem using @Chief-Lutz's heightForRowAt suggestion (outlined immediately above), I subsequently realised there was another solution which would work equally well. Instead of running height calculations inside heightForRowAt, it's possible to have multiple nibs, each designed to cope with differing numbers of images. The upside of this is that height calculations take place under the hood, making your code simpler and shorter; whilst the downside is having multiple nibs, probably with some repeated layouts and/or constraints within them.

This approach turns out to be particularly useful if your cells contain one or more zero-line UILabel's, because part of the calculation needed in heightForRowAt in this case involves [re]creating the cells UILabel's and populating them with text, so that they can resize and tell you how high each of them will be within the cell when it is rendered (heightForRowAt is called before cellForRowAt).

Upvotes: 0

Chief Lutz
Chief Lutz

Reputation: 94

I solved this problem in my project by using the heighForRow function.

In the ViewController:

 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    //Check if you have to display images or not

    //return 75 or CGFloat.ulpOfOne 
}

Maybe with this solution you have to make your let numImages = getRandomInt( from: 0, to: 2 ) before the cellForRowAtIndexPath function and store the 64 results in an Array.

EDIT: Important note from Josh Homann:

Do not return 0 for the height or you may get some auto layout errors in the console because your rect is degenerate. return CGFloat.ulpOfOne which is infinitesimally small but still non zero.

Upvotes: 1

Related Questions