Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with AutoLayout on UITableViewCell

I'm having trouble with autolayout on an xcode 5 project. I am using a plain view controller inside with a navigation controller. I have a MKMapView on the top half and a UITableView on the bottom half. I am using storyboards, and have configured the prototype UITableViewCell, but I am adding the constraints through code. I have double-checked every control in the prototype and don't see any constraints configured there. My problem occurs when I add the constraints for the UITableViewCell. I have the following code in the cells:

-(void)updateConstraints {     [super updateConstraints];     //first remove old constraints     [self removeConstraints:self.constraints];     [self.nameLabel removeConstraints:self.nameLabel.constraints];     [self.addressLabel removeConstraints:self.nameLabel.constraints];     [self.rentableSquareFeetLabel removeConstraints:self.rentableSquareFeetLabel.constraints];     [self.lastSaleAmountLabel removeConstraints:self.lastSaleAmountLabel.constraints];     [self.lastSaleDateLabel removeConstraints:self.lastSaleAmountLabel.constraints];     [self.thumbnailImageView removeConstraints:self.thumbnailImageView.constraints];      //then set up constraints     NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_thumbnailImageView, _nameLabel, _rentableSquareFeetLabel, _lastSaleAmountLabel, _addressLabel, _lastSaleDateLabel);     [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_thumbnailImageView(60)]-[_nameLabel(<=200)]-(>=8)-[_rentableSquareFeetLabel]-(>=8)-[_lastSaleAmountLabel]|" options:0 metrics:nil views:viewsDictionary]];     [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_nameLabel]-(-4)-[_addressLabel]" options:NSLayoutFormatAlignAllLeading metrics:nil views:viewsDictionary]];     [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_lastSaleAmountLabel]-(-4)-[_lastSaleDateLabel]" options:NSLayoutFormatAlignAllLeading metrics:nil views:viewsDictionary]]; } 

I am getting the following in the debugging console. The exception is triggered by the first addConstraints line. If I just continue through those then eventually everything shows up as it should be, as it looks like xcode is choosing to break the correct constraint:

2013-09-25 15:07:14.169 PECProperties[32381:a0b] 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)  (     "<NSIBPrototypingLayoutConstraint:0x9d56c70 'IB auto generated at build time for view with fixed frame' H:|-(0)-[UIImageView:0x9d558f0](LTR)   (Names: '|':UITableViewCellContentView:0x9d55620 )>",     "<NSIBPrototypingLayoutConstraint:0x9d56d20 'IB auto generated at build time for view with fixed frame' H:[UIImageView:0x9d558f0(60)]>",     "<NSIBPrototypingLayoutConstraint:0x9d56d80 'IB auto generated at build time for view with fixed frame' H:|-(78)-[UILabel:0x9d559e0](LTR)   (Names: '|':UITableViewCellContentView:0x9d55620 )>",     "<NSLayoutConstraint:0x9d53830 H:[UIImageView:0x9d558f0]-(NSSpace(8))-[UILabel:0x9d559e0]>" )  Will attempt to recover by breaking constraint  <NSIBPrototypingLayoutConstraint:0x9d56d80 'IB auto generated at build time for view with fixed frame' H:|-(78)-[UILabel:0x9d559e0](LTR)   (Names: '|':UITableViewCellContentView:0x9d55620 )>  Break on objc_exception_throw to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful. 

The third NSIBPrototypingLayoutConstraint shows 78 points between the edge of the view and a label. That is where the prototype is positioned roughly (and if I move it in the prototype, I see the change in the constraint in the debugging console), but that conflicts with my own constraint of "standard" distance between the image view and the label.

I have tried setting the translatesAutoresizingMaskIntoConstraints=NO in the view controller's cellForRowAtIndexPath, but that doesn't seem to be helping either. How can I fix the layout?

like image 883
Stephen Moore Avatar asked Sep 25 '13 21:09

Stephen Moore


People also ask

What is IOS Autolayout?

Auto Layout is a constraint-based layout system. It allows developers to create an adaptive interface that responds appropriately to changes in screen size and device orientation.


1 Answers

A few things to cover here:

  1. The NSIBPrototypingLayoutConstraint constraints that you're running into (and that are causing exceptions) are auto-generated by Interface Builder in order to make your Storyboard or XIB view layout non-ambiguous. It's pretty sneaky about doing this, but it's automatically adding the minimum constraints required so that the position and size of each ambiguous view becomes fully specified. This is a change from Xcode 4, because in Xcode 4 you could not have ambiguous layouts in Interface Builder. With Xcode 5 and later you can, however IB will auto-generate these constraints for you if your layout is ambiguous at compile time.

    The way to fix this issue is to add the minimum required constraints in Interface Builder so that each view's position & size is fully specified, then select each of these unwanted constraints, go to the right sidebar Attributes inspector, and check the box next to Placeholder - Remove at build time.

    Screenshot of the constraint Placeholder checkbox in Xcode 6.

    Not only does this checkbox remove the constraint you added, but most importantly it will prevent the auto-generated IB constraint from taking its place! (As you can imagine, this is quite tedious when you have a number of views in IB and want to manage all your constraints in code. For this reason you may want to avoid using IB entirely for view hierarchies in which you intend to implement Auto Layout programmatically.)

    What is the difference between a Placeholder constraint and an Uninstalled constraint? Here's a slide from my Adaptive Auto Layout talk (video) (PDF slides) comparing the two:

    Comparison between Placeholder constraints and Uninstalled constraints.

  2. In updateConstraints, you don't want to remove constraints and re-add them like you have there. Why not? Essentially, it's terrible for performance, and I have confirmed with Apple engineers that this is not a good idea. See the question/answer I have posted here for some more details, as well as this answer. In order to prevent constraints being added more than once, use a boolean flag (e.g. hasSetupConstraints) that you set to YES once you have set up your constraints the first time, and if updateConstraints is called again you can just return immediately if you have no new constraints to add. See this question for further discussion.

  3. The code you're using to remove constraints may not work completely. This is because [view removeConstraints:view.constraints] will only remove constraints that have been added to view -- remember that constraints can be added to any common superview of the views they constrain -- and the constraints added to view may not be the only ones affecting the layout of view! If you need to remove a number of constraints, you should store a reference to each of those constraints in a property (e.g. an NSArray property containing NSLayoutConstraint instances), and then deactivate/remove those constraints using the API on NSLayoutConstraint or the PureLayout open-source library. You should only deactivate/remove as few constraints as possible because it is computationally expensive to do so. On the other hand, changing the constant of any constraint is very efficient and encouraged, and you don't need to remove or re-add the constraint to do that.

like image 68
smileyborg Avatar answered Sep 17 '22 05:09

smileyborg