54

In my tableView I set a separator line between cells. I am allowing selection of multiple cells. Here's my code for setting selected cell background color:

UIView *cellBackgroundColorView = [[UIView alloc] initWithFrame:cell.frame];
[cellBackgroundColorView setBackgroundColor:[UIColor darkGray]];
[cell setSelectedBackgroundView:cellBackgroundColorView];

The problem is that if two adjacent cells are selected, there is no separator line between them in iOS7, while there is (as expected) in iOS6.

I even tried setting cellBackgroundColorView's frame height to that of cell.frame - 1.0, but that doesn't work either.

Any ideas?

artooras
  • 6,315
  • 9
  • 45
  • 78

24 Answers24

38

I haven't gotten to the bottom of it yet (at first glance it seems like an iOS 7 bug..), but I have found a workaround. In tableView:didSelectRowAtIndexPath, if you send both messages below, the issue is visually resolved (with the probable performance cost).

[tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

For this to work (for me), deselectRowAtIndexPath:animated: must contain animated:YES. The animation used for reloadRowsAtIndexPaths:withRowAnimation: doesn't matter.

Mark
  • 389
  • 2
  • 5
  • i was searching for this exact issue, but this didn't work for me, the cells just deselect and don't keep their background color i have set for the selected view. is there something I'm missing? – skinsfan00atg Jan 24 '14 at 16:21
  • My answer pertains only to the separator line disappearing after a cell is tapped. I had assumed the background color portion of the question was just supporting info. One of the workaround solutions below may be a better fit if you're having trouble with background color. – Mark Jan 24 '14 at 20:21
  • You're calling deselectRowAtIndexPath in didSelectRowAtIndexPath, won't that just automatically deselect any row you that select? Am I missing something? – HaloZero Mar 19 '14 at 20:38
  • 2
    @Mark, if I try using your code, it results in the cells I selected ending up unselected, which defeats the purpose. I want to have multiple (adjacent) cells selected (highlighted background) and the separator visible. – artooras Nov 09 '14 at 11:10
  • It works for `UIDatePicker` that hidden inside the cell `tableView.beginUpdates() // 1 tableView.endUpdates() tableView.deselectRowAtIndexPath(indexPath, animated: true) // 2 tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None) //3` 1) Update cell to change its height with animation 2) I omit this line because I have cell selection style `none` 3) `tableView` updates break `reloadRowsAtIndexPaths`, so I used row animation `none` here – Lion Aug 16 '15 at 20:32
  • As I think @Lion is saying, this works for me if I use animation:UITableViewRowAnimationNone on the reload; if I use 'automatic', the separator disappears and takes the cell content with it. – Geoffrey Wiseman Nov 19 '15 at 15:31
  • This should be the "Mark"ed answer! Thanks! – Hwangho Kim Jan 09 '16 at 07:52
29

Add this code at cell for row at indexpath

cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.backgroundColor = [UIColor clearColor];
Florent
  • 12,310
  • 10
  • 49
  • 58
srivatasa
  • 307
  • 3
  • 2
  • This was the fix for me. – Nerrolken Oct 03 '14 at 19:12
  • This is the correct answer. The answer from @Mark is plain wrong.It deselects what should stay selected, which doesn't make any sense. – Earl Grey Mar 21 '15 at 15:00
  • 14
    I'm confused! `cell.selectionStyle = UITableViewCellSelectionStyleNone;` makes selecting the cells impossible! how can this solve the question at hand? – M. Porooshani May 04 '15 at 10:58
  • Thanks!! Worked for me. – Kishore Jun 23 '15 at 18:57
  • It is important to note that setting selectionStyle= UITableViewCellSelectionStyleNone prevents UITableViewController.clearsSelectionOnViewWillAppear=YES from working as expected. The VC will not render the deselection animation of the cell in viewWillAppear when popped to in nav controller. – Brody Robertson May 11 '16 at 20:21
16

in my case i was animating a row, so just i needed put some like this:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

[tableView beginUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:NO]; 
//if you are doing any animation you have deselect the row here inside. 
[tableView endUpdates];
} 
GOrozco58
  • 1,182
  • 12
  • 10
  • 1
    This works still in iOS 9, animating a cells height one of my tableview lines vanished. This fixed it. I called this from didSelectRowAtIndexPath, from within my animation block. – DogCoffee Jan 10 '16 at 07:08
  • It will work too (in iOS9) when I omit begin- and endUpdates. In Both cases I loose the deselect animation though.... Any ideas? – blackjacx Mar 31 '16 at 16:34
  • While this works, you have to be careful with beginUpdates/endUpdates blocks that nothing is changing in your data source, otherwise you will get runtime exceptions. – Marc Etcheverry Feb 19 '20 at 00:09
12

@samvermette's answer solved the issue for me, But I had to deselect the selected Row first.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {

    //Deselect Row
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];

    // fix for separators bug in iOS 7
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; }
Community
  • 1
  • 1
Jamal Zafar
  • 2,179
  • 2
  • 27
  • 32
  • 2
    If your table has multiple selection option enabled (ie. when you need to use the checkmark accessory view) the behavior is conflicting with your solution. The problem is the deselectRowAtIndexPath:animated: method called when selecting. Is there another way? – wildmonkey Jan 21 '15 at 16:53
  • I would never get there by myself. Great solution. – brduca Jul 08 '15 at 14:15
7

I encountered this issue when I set my cell's selection style to none programatically, and then when I SELECT my table cells programatically.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell!
    if tableView == self.jobLevelTableView {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as! CheckboxCell

        // for testing purposes
        let checked = true

        // I used M13Checkbox here, in case anybody was wondering
        cell.checkbox.setCheckState(checked ? .checked : .unchecked, animated: false) 

        if checked {
            tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
        }

        // CULPRIT
        cell.selectionStyle = .none
        return cell
    }

    cell = UITableViewCell()
    return cell
}

When I set the selection style on the storyboard (and removing the code equivalent), the problem went away!

Storyboard is useful

remingtonchan
  • 479
  • 4
  • 11
7

Past it in your UITableViewCell class.

override func layoutSubviews() {
    super.layoutSubviews()

    subviews.forEach { (view) in
        if type(of: view).description() == "_UITableViewCellSeparatorView" {
            view.alpha = 1.0
        }
    }
}
Morgachev
  • 136
  • 2
  • 6
4

This still seems to be a problem as of iOS 7.0.3, but I've worked around it with an unsophisticated means of faking the separator.

By first setting the UITableView's separator style to UITableViewCellSeparatorStyleNone. You can then use a custom UITableViewCell subclass to fake the separator between cells for both selected and unselected states:

@implementation MyTableViewCellSubclass

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {

        CGRect frame = self.bounds;
        frame.origin.y = frame.size.height - 1.f;
        frame.size.height = 1.f;

        // Selected background view
        //
        UIView * separatorView = [[UIView alloc] initWithFrame:frame];
        separatorView.backgroundColor = [UIColor darkGrayColor];
        separatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin;

        UIView * selectedView = [[UIView alloc] initWithFrame:self.bounds];
        selectedView.backgroundColor = [UIColor lightGrayColor];
        [selectedView addSubview:separatorView];

        self.selectedBackgroundView = selectedView;

        // Add separator view to content view for unselected state
        //
        UIView * separatorView2 = [[UIView alloc] initWithFrame:frame];
        separatorView2.backgroundColor = [UIColor darkGrayColor];
        separatorView2.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin;

        [self.contentView addSubview:separatorView2];


    }
    return self;
}


@end
sho
  • 759
  • 5
  • 16
4

This simple call did it for me on iOS 8.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // ....
    [tableView deselectRowAtIndexPath:indexPath animated:YES]

    // ....
}
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
3

This'll just happen if you let iOS apply its own default selected cell style. Best work around I found so far is to override the selected property implementation:

in your cell subclass implementation:

    @synthesize selected = _selected;

in the initialization method:

    // problem actually is caused when you set following 
    // to UITableViewCellSelectionStyleDefault, so:

    [self setSelectionStyle:UITableViewCellSelectionStyleNone]; 

overriding methods:

    - (BOOL)selected 
    {
        return _selected;
    }
    - (void)setSelected:(BOOL)selected animated:(BOOL)animated 
    {
        _selected = selected
        if (selected) {

            // apply your own selected style

        }
        else {

            // apply your own deselected style

        }
    }
JackyJohnson
  • 3,106
  • 3
  • 28
  • 35
  • Brilliant! This works perfectly in iOS 9. It also maintains the separator lines top and bottom when you simply change the cell background color instead of adding a selected view. Great stuff! – David H Jul 01 '16 at 15:06
  • Actually I had a transparent color I needed to use, so I added a backgroundView and then animated its backgroundColor. Separators stay visible!!! – David H Jul 01 '16 at 15:23
2

I resolved this issue (hackishly) by reloading not just the selected cell but by also reloading the one right above it. None of the other solutions above worked for me.

    NSIndexPath *indexPathOfCellAbove = [NSIndexPath indexPathForRow:(indexPath.row - 1) inSection:indexPath.section];

    if (indexPath.row > 0)
        [self.tableView reloadRowsAtIndexPaths:@[indexPathOfCellAbove, indexPath] withRowAnimation:UITableViewRowAnimationNone];
    else
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
BigSauce
  • 1,830
  • 3
  • 21
  • 27
2

- cellForRowAtIndexPath

Create two separator views (sv1, sv2)

[cell addsubview:sv1];
[cell.selectedBackgroundView addsubview:sv2];

- didSelectRowAtIndexPath

[tableView deselectRowAtIndexPath:indexPath animated:NO];
Praveen Matanam
  • 2,773
  • 1
  • 20
  • 24
2

In iOS 14, Apple has FINALLY made this less painful.

If you want to...

  • ...be able to select rows (for example in edit mode)
  • ...prevent the default gray or blue cell highlight color
  • ...keep the default system separator views

...this will help you. In your UITableViewCell subclass, put this into the initializer:

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    // Prevent cell highlighting while preserving selectability and separator views
    if #available(iOS 14.0, *) {
        var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
        backgroundConfig.backgroundColor = .clear
        backgroundConfiguration = backgroundConfig
    } else {
        selectedBackgroundView = {
            let bgView = UIView(frame: .zero)
            bgView.translatesAutoresizingMaskIntoConstraints = false
            bgView.backgroundColor = .clear
            return bgView
        }()
    }
}

If you're only targeting iOS 14+ you can leave out the else block and you're done. If you are also targeting iOS 13 and below, you'll also need to override layoutSubviews to keep the separator view from disappearing (thanks to this comment: https://stackoverflow.com/a/47573308/171933). This will do the trick (also in your UITableViewCell subclass):

override func layoutSubviews() {
    super.layoutSubviews()

    if #available(iOS 14.0, *) {
        // no op
    } else {
        // Setting a custom selectedBackgroundView causes the system to hide the
        // separatorView. If we want to have the separator, we need to show it again.
        subviews.forEach { view in
            if type(of: view).description() == "_UITableViewCellSeparatorView" {
                view.alpha = 1.0
            }
        }
    }
}

Enjoy.

Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165
1

For me it happened when I set programmatically:

cell.selectionStyle = UITableViewCellSelectionStyleNone;

When i set this property in the storyboard it works fine.

Patrick Haaser
  • 353
  • 1
  • 3
  • 10
0

You could also trying setting the separator insets to 0. I did that and it solved the problem, but the trade-off is you lose the nice look of the insets.

skantner
  • 480
  • 4
  • 19
0

This problem exists for single cell selection as well.

Another solution is to reload the table view, select followed by deselect:

self.selectedIndex = inIndexPath.row;
[inTableView reloadData];
[inTableView selectRowAtIndexPath:inIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[inTableView deselectRowAtIndexPath:inIndexPath animated:YES];

This gets rid of a subtle graphical selection glitch I saw in Mark's solution.

Morrowless
  • 6,856
  • 11
  • 51
  • 81
0

this solution will not help anybody who isn't using a backgroundView on his cells, anyway:

- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        [cell setBackgroundColor:[UIColor grayColor]];
    }

this way the annoying visual effect is vastly reduced without having to reload the table. of course, you can change grayColor with anything which helps you improve the result in your case

Fabio Napodano
  • 1,210
  • 1
  • 16
  • 17
0

use this:

- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
    UIView *selectionColor = [[UIView alloc] init];
    selectionColor.backgroundColor = [UIColor clearColor];
    cell.selectedBackgroundView = selectionColor;
    //71
    UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 71, 320, 2)];/// change size as you need, where - 71 - y coordinate, 320 - weight, 2 - height
  //  separatorLineView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"divider_goriz.png"]];// you can also put image here
    separatorLineView.backgroundColor = [UIColor redColor];
    [cell.selectedBackgroundView addSubview:separatorLineView];
    return YES;
}
Alex Kraev
  • 199
  • 1
  • 12
0

What I did was this:

  1. Add a new subview under the content view of the cell.
  2. Connect that from the cell as the selectedBackgroundView.
  3. Add a subview of the new selected background view. Set it to start 16px from the left and cover the rest of the width, be 1px high, 1px down from the top and have a background color of 90% white.

In my case, I didn't want my rows shaded at all when selected, so I left the selected background view clear, but you can make it whatever color you like.

Also, I am not using autolayout, so just set my sizes appropriately. I presume with autolayout you would have to set up appropriate constraints.

For me, this completely resolved the problem (though I agree that this really does seem to be a bug in ios 7).

Gideon King
  • 199
  • 1
  • 5
0

The solutions here didn't help me. In most cases it was proposed to remove the selection, but I wanted the cells to keep their selected state. So the idea is to disable the default separator line and use your own separator line. I tried this but I had problems with it (you can read more about this here). The main problem was drawing the line in the accessoryView area. It only worked on iOS 8, but I also needed a solution for iOS 7.

My requirements were:

  • Selection should be kept
  • Line should not disappear (especially in the case the cell get selected)
  • Separator line above the selected cell should also not disappear

Especially the third point made problems because iOS uses a kind of anti-aliasing effect for the crossing of on UITableViewCell to the next. As I found out that only occurs on iPad. It has the size of about one point in each direction (current selected cell, cell above) so that a line on the cell disappears even it is drawn on the cell itself (and not the default one used). It makes no difference if this line is on the cell above or on the selected cell. This special render effects hides my lines.

The solution looks like the following:

  1. Use the backgroundView where you draw two lines: one on top (+1 point in y-direction for iPad and 0 point in y-direction for iPhone) and one on the bottom. So it never gets covered by the selection effect.
  2. The created background view should only be used for the selected state (cell.selectedBackgroundView = selectedBackground). The default separator line is enabled for the other cells.

I have a working example with C# code posted here though you have to adapt it to your needs. Now my selection problems are gone!

shim
  • 9,289
  • 12
  • 69
  • 108
testing
  • 19,681
  • 50
  • 236
  • 417
0

Too exciting, I solved this problem. Add the following method call in a custom cell, and to set the color separator and frame. I'll hide the cell separator, and then customize the view on a load separator in superview. The impact separator cell is selected when this problem is solved friends

@interface MyCustomTableViewCell(){
   UIView *customSeparatorView;
   CGFloat separatorHight;
}
@property (nonatomic,weak)UIView *originSeparatorView;
@end

-(void)setSeparatorWithInset:(UIEdgeInsets)insets{

if (customSeparatorView) {
    customSeparatorView.frame = CGRectMake(insets.left, insets.top,self.width - insets.left - insets.right, self.originSeparatorView.height-insets.bottom - insets.top);
    self.originSeparatorView.hidden = YES;
    self.originSeparatorView.alpha = 0;
}else{
    for (int i = ([self.contentView.superview.subviews count] - 1); i >= 0; i--) {
        UIView *subView = self.contentView.superview.subviews[i];
        if ([NSStringFromClass(subView.class) hasSuffix:@"SeparatorView"]) {
            self.originSeparatorView = subView;
            subView.hidden = YES;
            subView.alpha = 0;
            subView.frame = CGRectMake(insets.left, insets.top,self.width - insets.left - insets.right, subView.height-insets.bottom - insets.top);

            customSeparatorView = [[subView superview] viewWithTag:separatorViewTag];
            if (!customSeparatorView) {
                customSeparatorView = [[UIView alloc] initWithFrame:subView.frame];

                customSeparatorView.tag = separatorViewTag;
                [[subView superview] addSubview:customSeparatorView];
                customSeparatorView.backgroundColor = [subView backgroundColor];
            }
            [[subView superview] bringSubviewToFront:customSeparatorView];
            break;
        }
    }
  }
}


-(void)setSeparatorColorWithColor:(UIColor *)sepColor{
if (customSeparatorView) {
    customSeparatorView.backgroundColor = sepColor;

    self.originSeparatorView.hidden = YES;
    self.originSeparatorView.alpha = 0;
}else {
    for (int i = ([self.contentView.superview.subviews count] - 1); i >= 0; i--) {
        UIView *subView = self.contentView.superview.subviews[i];
        if ([NSStringFromClass(subView.class) hasSuffix:@"SeparatorView"]) {
           self.originSeparatorView = subView;

            if (sepColor) {
                subView.hidden = YES;
                subView.alpha = 0;
                subView.backgroundColor = sepColor;

                customSeparatorView = [[subView superview] viewWithTag:separatorViewTag];
                if (!customSeparatorView) {
                    customSeparatorView = [[UIView alloc] initWithFrame:subView.frame];

                    customSeparatorView.tag = separatorViewTag;
                    [[subView superview] addSubview:customSeparatorView];
                    customSeparatorView.backgroundColor = [subView backgroundColor];
                }
                [[subView superview] bringSubviewToFront:customSeparatorView];
            }
            break;
        }
    }
  }
}

 -(void)layoutSubviews{
    [super layoutSubviews];
    [self setSeparatorWithInset:UIEdgeInsetsMake(0, 0, 0, 0)];
    [self setSeparatorColorWithColor:[UIColor colorWithRed:31/255.0 green:32/255.0f blue:35/255.0 alpha:0.2]];
 }
0

what solved the issue for me was reloading the data after beginUpdates and endUpdates:

    private func animateCellHeighChangeForTableView(tableView: UITableView, withDuration duration: Double) {
        UIView.animateWithDuration(duration) { () -> Void in
           tableView.beginUpdates();
           tableView.endUpdates();
           tableView.reloadData();
        }
    }
Ali M
  • 67
  • 1
  • 8
0

I needed the following:

"When user selects row, selection background color is transparent/white/whatever you may call it and separator lines don't disappear"

I've looked as well for a solution for the following problem:

"When I select a row in a table (plain type table) I had selection colour grey, and if I set cell.selectionStyle to none -> Separators between cells disappeared."

Xcode - 9.2 version

Found the following solution:

  1. in 'tableView (....cellForRowAT...)' let colorView = UIView(frame: CGRect(x: 0.0, y: 3.0, width: cell.frame.width, height: cell.frame.height - 1.0)) colorView.backgroundColor = UIColor.white UITableViewCellClass.appearance().selectedBackgroundView = colorView

UITableViewCellClass - is your prototype cell class it makes possible to change selection color to white

  1. in 'tableView (...didSelectRowAt)' cell.selectionStyle = .none

  2. in UITableViewCellClass (your prototype cell class)

    override func layoutSubviews() { super.layoutSubviews()

    subviews.forEach { (view) in
        if type(of: view).description() == "_UITableViewCellSeparatorView" {
            view.alpha = 1.0
        }
    }
    

    }

it allows to keep selected row with check mark and all separators are in place.

shim
  • 9,289
  • 12
  • 69
  • 108
0

I encountered this problem with IOS 13 and solved it in this way:

  1. In tableView cell storyboard or xib file choose Selection NONE

  2. In swift file of the cell override func:

    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    
        super.setHighlighted(highlighted, animated: animated)
    
        if highlighted {
            contentView.backgroundColor = .lightGray
        } else {
            contentView.backgroundColor = .clear
        }
    }
    

I wanted to get effect of regular selection like in previous IOS versions but if you want to get something else then customize the function with your colors.

Mile Dev
  • 652
  • 6
  • 13
-2

For those of you looking for a solution in Swift, this fixed the issue for me. In your cellForRowAtIndexPath method, after you call dequeueReusableCellWithIdentifier, you just need to set the cells selectionStyle to .None

Here's the code:

    override func tableView(tableView: UITableView?,  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell:TextTableViewCell = tableView!.dequeueReusableCellWithIdentifier("textCell", forIndexPath: indexPath) as! TextTableViewCell

    cell.selectionStyle = .None //  This fixes the disappearing border line issue
Jakoby
  • 52
  • 7