Hi,
I am experiencing a particular behavior of dynamic table view cells with autolayout that has me stumped. The problem is regarding a cell containing an image view of fixed size. Assume, however, that the table view cannot assume fixed heights for cells, that there exist a non-zero number of other cells that do not have an image view of fixed size. Also assume that image assets cannot be customized at build time for the fixed size (i.e., the assets are not the same size as the image view's fixed size).
The behavior is, I either have too few constraints or too many constraints for the dynamic table view cell height, and I haven't been able to figure out how to squelch the runtime debugger warnings. I've reproduced the problem with a basic storyboard, table view controller and table view cell, and a relatively small number of steps:
- Create a Single View application from the template
- Replace the ViewController with a DynamicTableViewController, a subclass of UITableViewController
- Implement DynamicTableViewController in Swift as follows:
class DynamicTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mainCell") as! DynamicTableViewCell
cell.configureView(atRow: indexPath.row)
return cell
}
}
Implement a DynamicTableViewCell in Swift as follows:
class DynamicTableViewCell: UITableViewCell {
@IBOutlet weak var myImage: UIImageView!
@IBOutlet weak var myLabel: UILabel!
func configureView(atRow row: Int) {
myImage.image = UIImage(named: "MissingImage")
myLabel.text = "I am at row \(row)"
}
}
- In the storyboard, set the class name and identifier of the prototype cell to DynamicTableViewCell and "mainCell" to match the code above
- In the storyboard, set the prototype cell row height to 72 to exaggerate the problem that will occur later
- In the storyboard, create a vertically centered UIImageView on the left hand side, and a UILabel to the right of it
- In the storyboard, wire up the myImage and myLabel IBOutlets
- In your favorite image asset creation tool, create 1x, 2x and 3x versions of a simple box image of any size (1x = 44 x 44 is sufficient, ironically) and add them with the name "MissingImage" to your xcassets folder
- In the storyboard, wire up the myImage and myLabel IBOutlets
Now we're getting to the good part. Let's add autolayout constraints!
- In the storyboard, set width and height constraints on the image at 44 x 44
- In the storyboard, set top and leading constraints on the image to the container margin, and vertical center and horizontal spacing on the label relative to the image
Notice there should be no warnings in the storyboard regarding constraints.
Run the app
Notice that the cells are clipped, and there is a warning in the debugger log:
"TestDynamicTableViewCellHeight[29742:7156065] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead."
My interpretation is, the table view could not determine the dynamic height of the cell because there aren't enough constraints from the top to the bottom of the cell content view, because it is using dynamic row heights. I'm OK with that, but what happens next is what is really perplexing me.
Add a bottom constraint on the image
Notice there are still no warnings in the storyboard regarding constraints.
Run the app
Notice that the cells are no longer clipped and everything is peachy, except that there are now 3 instances of this warning in the debugger log:
TestDynamicTableViewCellHeight[29768:7160653] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x7ff4d49a30d0 V:[UIImageView:0x7ff4d49a2f10(44)]>",
"<NSLayoutConstraint:0x7ff4d49b0250 UIImageView:0x7ff4d49a2f10.top == UITableViewCellContentView:0x7ff4d49a2910.topMargin + 6>",
"<NSLayoutConstraint:0x7ff4d49b02a0 UITableViewCellContentView:0x7ff4d49a2910.bottomMargin == UIImageView:0x7ff4d49a2f10.bottom + 5.5>",
"<NSLayoutConstraint:0x7ff4d49d84e0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7ff4d49a2910(71.6667)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7ff4d49a30d0 V:[UIImageView:0x7ff4d49a2f10(44)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
So, what do I need to do to keep the table view row heights dynamic and also have a cell containing an image of fixed constraint size that determines the cell's height, such that the above constraint problem does not occur, and also no clipping occurs?