1

I've decided to programmatically create my own UITableView separator lines because I need fine control over displaying a separator above and/or below each individual UITableViewCell. My tableView has static cells, so I do not create the separators in cellForRowAtIndexPath. Instead, I have propertys for each cell and in viewDidLoad, I add a top and/or bottom separator as needed. It's working, until I rotate to landscape and then the separator line does not stretch to fill the screen - it of course remains the same width it was when created. I'm not seeing how I can automatically adjust them to fit the width of the screen.

I tried adding Auto Layout constraints (leading, trailing, top/bottom), but for some reason it's not working - the width does not change, but there are no error messages logged to indicate anything is wrong with the constraints. The separator lines also sometimes disappear upon scroll or rotate, and if I comment out the auto layout constraints then they do not disappear.

So how can I make my custom cell separators always stretch to fill the device width upon rotation, and how do I prevent them from disappearing?

If it would be easier/better to create my custom cell separators in a different way, I am willing to do that. I just don't know how this can be done aside from my approach when the cells are static. I considered creating the views in the Storyboard, and setting up the constraints visually, but would that not be the equivalent of what I'm doing programmatically? If they were dynamic cells I would do it in cellForRowAtIndexPath.

//In viewDidLoad:
[self addTopSeparatorForCell:self.myCell];

//Helper method
- (void)addTopSeparatorForCell:(UITableViewCell *)cell {
    UIView *topSeparator = [[UIView alloc] initWithFrame:CGRectMake(15, 1, cell.contentView.frame.size.width, 0.5)];

    //add CALayer to preserve line separator visibility when row is highlighted
    CALayer *backgroundColorLayer = [CALayer layer];
    backgroundColorLayer.frame = topSeparator.bounds;
    backgroundColorLayer.backgroundColor = [UIColor colorWithWhite:204/255.0f alpha:1].CGColor;

    [topSeparator.layer addSublayer:backgroundColorLayer];

    [cell.contentView addSubview:topSeparator];

    //add auto layout constraints
    topSeparator.translatesAutoresizingMaskIntoConstraints = NO;
    NSLayoutConstraint *cn = nil;
    cn = [NSLayoutConstraint constraintWithItem:topSeparator
                                      attribute:NSLayoutAttributeLeading
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:cell.contentView
                                      attribute:NSLayoutAttributeLeading
                                     multiplier:1.0
                                       constant:15];
    [cell.contentView addConstraint:cn];
    cn = [NSLayoutConstraint constraintWithItem:topSeparator
                                      attribute:NSLayoutAttributeTrailing
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:cell.contentView
                                      attribute:NSLayoutAttributeTrailing
                                     multiplier:1.0
                                       constant:0];
    [cell.contentView addConstraint:cn];
    cn = [NSLayoutConstraint constraintWithItem:topSeparator
                                      attribute:NSLayoutAttributeTop
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:cell.contentView
                                      attribute:NSLayoutAttributeTop
                                     multiplier:1.0
                                       constant:1];
    [cell.contentView addConstraint:cn];
}

enter image description here

EDIT: Thanks to @ user1966109, we've been able to solve the issue with the lines not extending to fill the width, and now they are preserved when highlighting a cell. But one issue still remains that I haven't been able to solve, since I'm not sure why it's occurring. The separator lines disappear after scrolling down the scrolling back up. It's related to the auto layout constraints though because a previous solution which had other issues did not exhibit this problem. Here's the current solution that causes the lines to disappear. I'd appreciate it if someone knows how to prevent this problem (and preserve the other issues already resolved).

[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(15@750)-[myView]-(-47@750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[myView(2)]-(-2@750)-|" options:0 metrics:0 views:viewsDictionary]];
Jordan H
  • 52,571
  • 37
  • 201
  • 351

2 Answers2

1

You should not mix initWithFrame and Auto Layout. You can have a good result with a few lines using Visual Format Language for Auto layout:

//@interface TableViewController ()
@property (weak, nonatomic) IBOutlet UITableViewCell *cell;

//@implementation TableViewController
- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView *myView = [[UIView alloc] init];
    myView.backgroundColor = [UIColor redColor];

    [self.cell.contentView addSubview:myView];    
    myView.translatesAutoresizingMaskIntoConstraints = NO;
    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(myView);

    [self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[myView]|" options:0 metrics:0 views:viewsDictionary]];
    [self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[myView(2)]" options:0 metrics:0 views:viewsDictionary]];
}

This handles rotation perfectly.

Edit!

Set the following constraints if using a accessory view:

//Set a negative value to the trailing space in order to display myView under the accessory view
//Those constraints work for both self.cell and self.cell.contentView (kind of odd)
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(15@750)-[myView]-(-47@750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[myView(2)]-(-2@750)-|" options:0 metrics:0 views:viewsDictionary]];
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • Thank you, that's working really well! There is just one issue. When a cell has an accessoryType (disclosure indicator for example), the separator line stops at that view not the right edge of the screen. How can I enforce it always goes to the edge? Cheers. – Jordan H May 23 '14 at 16:11
  • You're right. I had not thought about it... Yet, I could use a trick to deal with it (see my post edit). – Imanou Petit May 23 '14 at 21:52
  • Thanks, I'll try that when I get back to work after the holiday. :) I noticed one other issue albeit minor. When you tap a cell it becomes highlighted and iOS makes the custom views disappear. I solved this in my first solution with `CALayer` but I can't use that anymore because the bounds aren't defined. Can you think of a solution to preserve the lines upon highlighting the cell with this new approach? – Jordan H May 24 '14 at 04:02
  • This is a known situation: when a UITableViewcell is selected, backgroundColors of all UIViews inside its contentView disappear. Several solutions exist (see [here](http://stackoverflow.com/a/12838659/1966109) or [here](http://stackoverflow.com/a/6746201/1966109)). – Imanou Petit May 24 '14 at 22:43
  • I've looked at those questions, and that's how I stumbled upon the CALayer workaround. But I'm not subclassing that cell. Perhaps there's another solution not requiring subclassing. – Jordan H May 25 '14 at 02:01
  • In the second link, @Andriy proposes to replace `UIView *myView` with `UIImageView *imageView` and to add to this imageView a 1x1 px colored image. I tried and it works: the imageView.image doesn't disappear when the cell is selected. – Imanou Petit May 25 '14 at 02:28
  • Interesting. I shall try that as well, will report back. Thanks for all the suggestions! – Jordan H May 25 '14 at 02:36
  • Hey @user1966109, those new constraints do cause the line to display correctly when using an accessory view, but the separators disappear after scrolling down so it's not visible upon scrolling back up. But that `UIImageView` instead of `UIView` worked perfect for preserving the separators upon row selection. :) It's almost perfect now, I'm just not sure how to prevent them from disappearing upon scroll. Would appreciate it if you know how to fix that and can help out. – Jordan H Jun 02 '14 at 16:04
0

With the initial help of user1966109, I have figured out constraints that address all of the problems and are working well:

    [cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
                                                     attribute:NSLayoutAttributeLeading
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:cell
                                                     attribute:NSLayoutAttributeLeading
                                                    multiplier:1.0
                                                      constant:indent]];
    [cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
                                                     attribute:NSLayoutAttributeTrailing
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:cell
                                                     attribute:NSLayoutAttributeTrailing
                                                    multiplier:1.0
                                                      constant:0.0]];
    [cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView(0.5)]" options:0 metrics:0 views:viewsDictionary]];
Jordan H
  • 52,571
  • 37
  • 201
  • 351