Reputation: 2264
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):
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
setting explicit references to all NSLayoutConstraint
's that control height in my ResizableImageCell
call; and/or
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:
Thank you for reading.
Upvotes: 1
Views: 95
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
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