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 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:
.
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.
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.
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.