Hiding Table Separators on a Cell-by-Cell Basis

September 17, 2014

Updated on 7/12/2016 to address some quirks of iOS 9.

It's very easy to turn off cell separators for an entire UITableView, however I frequently get asked to implement designs that have separators between most of the cells but not all of them. The required approach changes between iOS 6.x, 7.x, and 8.x, but it gets easier as the versions become more modern. Ideally Apple will improve UITableViewDelegate on iOS 9.0 to include a method like -(BOOL)tableView:(UITableView*)tableView shouldShowSeparatorBetweenCellsAtIndexPath:(NSIndexPath*)firstIndexPath andIndexPath:(NSIndexPath*)secondIndexPath to solve this problem officially.

iOS 6.x and earlier

iOS versions 6.0 and earlier can be a big pain because they require you to use a UITableViewCell subclass with some non-trivial changes. I'm not going to provide a full implementation because I don't ship any apps with a deployment target below iOS 7.0, however the basic gist of it is to set separatorStyle on the table to UITableViewCellSeparatorStyleNone and add custom top and bottom border views that you show or hide as needed in tableView:cellForRowAtIndexPath: or tableView:willDisplayCell:forRowAtIndexPath:.

iOS 7.x

Fortuitously, Apple added separatorInset to UITableViewCell in iOS 7.0, which is intended to be used to adjust how the separator is positioned. Prior to 7.0, separators were drawn from edge to edge, but changed in 7.0 to be drawn starting 15.0 points from the cell's left edge. To hide a separator on a specific cell, set its separatorInset to UIEdgeInsetsMake(0, 0, 0, CGRectGetWidth(self.bounds)).

I typically put this code in -layoutSubviews to ensure the separator is still fully hidden if the cell changes size (i.e., when the device is rotated). It can take some trial and error to make sure the inset succeeds in hiding the correct separator, but typically separatorInset controls the separator drawn below the cell.

iOS 8.x

In iOS 8.0 the previous trick doesn't work because UITableViewCell doesn't allow the right inset value to push the separtor past the left edge of the cell. Because negative values are pinned to 0, something like UIEdgeInsetsMake(0, -CGRectGetWidth(self.bounds), 0, 0) won't work either. Some trial and error led me to a new solution: UIEdgeInsetsMake(0, CGRectGetWidth(self.bounds)/2.0, 0, CGRectGetWidth(self.bounds)/2.0). This effectively forces the cell to squeeze the separator into a space 0 points wide.

iOS 9.x

The iOS 8 approach works on iOS 9, however you can't use it from cellForRowAtIndexPath: because the cell's bounds are not set to its eventual size on screen. In my testing cells are always initialized to 320 points wide, and on larger sized phones the separators end up being visible because CGRectGetWidth(self.bounds)/2.0 ends up calculating a value smaller than needed. Luckily the fix is easy: just set the separator inset in willDisplayCell:atIndexPath: where the cell bounds are accurate to its final size.