Too few/too many constraints with dynamic table view cells?

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?

Accepted Reply

OK, I followed your instructions and built this test app. Everything works fine and I get no warnings in the debugger. I'm using Xcode 7.0.1 on iPhone 6 Sim and real iPhone 6 iOS 9.0. Below is my constraints setup.



can you see that image?


The only odd thing is the vertical centering of the image view. If I add a centerY constraint to the imageView then there's a conflict between the centerY and top and bottom constraints. However if I change the Priority of the top and bottom constraints to 750 and leave the centerY at 1000 then there's no warnings in the debugger console.

Replies

I think the problem is that you've set both a center vertically constraint as well as top and bottom constraints for the image view. Try removing the center vertically constraint. You might be able to fix this by adjusting the priority of the three constraints so there's no conflict. Also, your bottom constraint has a constant of 5.5 while the top constraint is 6.


I assume you're doing this on iOS 8 or later.

No, that's not it. And yes I'm doing this on iOS 9. If you're following the steps, remove the UILabel and its Y centered constraint, it was an attempt at making the cell view useful. Without that label and the center vertically constraint, the constraint conflict persists.

OK, I followed your instructions and built this test app. Everything works fine and I get no warnings in the debugger. I'm using Xcode 7.0.1 on iPhone 6 Sim and real iPhone 6 iOS 9.0. Below is my constraints setup.



can you see that image?


The only odd thing is the vertical centering of the image view. If I add a centerY constraint to the imageView then there's a conflict between the centerY and top and bottom constraints. However if I change the Priority of the top and bottom constraints to 750 and leave the centerY at 1000 then there's no warnings in the debugger console.

Lowering the priority of the vertical space constraints to 750 did the trick!