J Kagney
J Kagney

Reputation: 239

AutoLayout not pinning subview to superview

I'm trying to pin a subview inside of a custom UITableViewCell class to the left side of the cell's contentView. I'm adding the subview to the contentView inside of the initWithStyle method of the subclass, as well as adding constraints inside of that method. Unfortunately, I have two problems:

  1. The subview is not pinning to the left edge of the contentView even though I'm using the following code to do so:

    [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_animatedView]-[_aTextField][_rightImageView]|"
                                                                             options:NSLayoutFormatDirectionLeftToRight
                                                                             metrics:nil views:viewDictionary]];
    

    To me, that format string should be pinning the _animatedView to the left side of the contentView, and then appending the other subsequent views as close as possible afterwards. Instead, all three views show up together in the proper order in the middle of the contentView, instead of at the left edge. I'm new to using autolayout, so this might be a confusion of how the format strings work on my part.

  2. Solved: Thanks to Guillaume's suggestion, I was able to do a little bit of testing and find out that the reason that the cell contents were wildly displaced as I scrolled through the table was that I had not overridden the UITableViewCell method, prepareForReuse. This method is called when a UITableViewCell is prepared to be reused to display information for another object in the UITableView, which incidentally happens when scrolling occurs. The solution was as simple as inserting the following code into my UITableViewCell subclass:

    -(void) prepareForReuse
     {
         [self setNeedsUpdateConstraints];
     }
    

The other problem is that the constraints appear to not be enforced until I select a row of the TableView where the custom cells are being used. Is there a place other than initWithStyle I should be setting up the constraints or is this a different issue?

Update:

CustomTableCell.m

@interface ListTableCell ()
{
    NSDictionary *_viewsForConstraints;    // Used inside updateConstraints method.
    NSArray *_animatedViewSizeConstraints;
    NSArray *_centeringConstraints;
    NSArray *_horizontalConstraints;
}
@end

 - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
       // Set up the cell elements.
       self.contentView.translatesAutoresizingMaskIntoConstraints = NO;

       self.animatedView = [[AnimatedView alloc] initWithFrame:CGRectZero];
       self.animatedView.translatesAutoresizingMaskIntoConstraints = NO;
       [self.contentView addSubview:self.animatedView];

       self.aTextField = [[UITextField alloc] initWithFrame:CGRectZero];
       self.aTextField.textColor = [UIColor whiteColor];
       self.aTextField.translatesAutoresizingMaskIntoConstraints = NO;
       [self.contentView addSubview:self.aTextField];

       self.rightImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
       self.rightImageView.translatesAutoresizingMaskIntoConstraints = NO;
       [self.contentView addSubview:self.rightImageView];

       _viewForConstraints = NSDictionaryOfVariableBindings(_animatedView,   
       _aTextField, _rightImageView);

       // Add height/width constraints to the animated view.
       NSLayoutConstraint *heightConstraint =
       [NSLayoutConstraint constraintWithItem:self.animatedView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1  constant:self.contentView.frame.size.height];
       NSLayoutConstraint *aspectRatioConstraint = [NSLayoutConstraint constraintWithItem:self.animatedView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:self.animatedview attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];

       _animatedViewSizeConstraints = @[heightConstraint, aspectRatioConstraint];

       // Center elements vertically.

       NSLayoutConstraint *animatedViewVerticalCenterConstraint =
       [NSLayoutConstraint constraintWithItem:self.animatedView
                                 attribute:NSLayoutAttributeCenterY
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:self.contentView
                                 attribute:NSLayoutAttributeCenterY
                                multiplier:1.0
                                  constant:0.0];
        NSLayoutConstraint *aTextFieldVerticalCenterConstraint =
        [NSLayoutConstraint constraintWithItem:self.aTextField
                                 attribute:NSLayoutAttributeCenterY
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:self.contentView
                                 attribute:NSLayoutAttributeCenterY
                                multiplier:1.0
                                  constant:0.0];
        NSLayoutConstraint *rightImageVerticalCenterConstraint =
        [NSLayoutConstraint constraintWithItem:self.rightImageView
                                 attribute:NSLayoutAttributeCenterY
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:self.contentView
                                 attribute:NSLayoutAttributeCenterY
                                multiplier:1.0
                                  constant:0.0];

       _centeringConstraints = @[animatedViewVerticalCenterConstraint,
                                 aTextFieldVerticalCenterConstraint, rightImageVerticalCenterConstraint];

       _horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[_animatedView]-[_aTextField][_rightImageView]|"
                options:NSLayoutFormatDirectionLeftToRight metrics:nil views:viewDictionary]];
       self.backgroundColor = [UIColor greenColor];
       self.editingAccessoryType = UITableViewCellAccessoryNone;
       self.accessoryType = UITableViewCellAccessoryNone;
       self.selectionStyle = UITableViewCellSelectionStyleNone;
       self.aTextField.userInteractionEnabled = NO;

       [self setNeedsUpdateConstraints];
  }

-(void) updateConstraints
{
    [super updateConstraints];

    [self.contentView addConstraints:_animatedViewSizeConstraints];
    [self.contentView addConstraints:_centeringConstraints];
    [self.contentView addConstraints:_horizontalConstraints];
}

TableViewController.m

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 {
    static NSString *CellIdentifier = @"Cell";
    CustomTableCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell.
    cell.aTextField.text = @"Test Text";
    cell.aTextField.delegate = self;

    return cell;
}

I can confirm that initWithStyle is getting called because the background is always green and subviews always show up, they just show up very oddly spaced initially.

Thank you for any help with this!

Upvotes: 3

Views: 3389

Answers (2)

Steven Hepting
Steven Hepting

Reputation: 12504

Don't turn off translatesAutoresizingMaskIntoConstraints of the contentView of your tableview.

I've run into this problem before.

Remove this line: self.contentView.translatesAutoresizingMaskIntoConstraints = NO;

Essentially, your view is pinning to the left edge of the contentView but the content view isn't the full width of the cell. You can use something like DCIntrospect-ARC or Reveal to see that it's the case.

Upvotes: 0

Guillaume Algis
Guillaume Algis

Reputation: 11016

You should setup your constraints in the updateConstraints method. From the doc:

Custom views that set up constraints themselves should do so by overriding this method.

That should at least solve your #2 issue.

Upvotes: 2

Related Questions