UITableViewCell Separator disappearing in iOS7

I have some strange issue with UITableView only in iOS 7.

UITableViewCellSeparator disappears above the first row and below the last row. Sometimes after selecting the rows or some scrolling actions it appears.

  • CCMenu misplaced under iOS 7
  • NSCFData fastCharacterContents crash?
  • Why does Google Chrome emulator show iPhone 6 at 375x667 resolution?
  • NSOperationQueue vs GCD
  • Firebase: Can I use Facebook's new Account Kit to authenticate app users?
  • Green screen / chroma key iOS
  • In my case tableView is loaded from the Storyboard with UITableViewStylePlain style. The problem is surely not in UITableViewCellSeparatorStyle, which is not changed from default UITableViewCellSeparatorStyleSingleLine.

    As I read at Apple Dev Forums (here and here) other people have such problem and some workarounds are found, for example:

    Workaround: disable the default selection and recreate the behaviour in a method
    trigged by a tapGestureRecognizer.
    

    But I am still searching for the reason of such separator strange behaviour.

    Any ideas?

    Update: As I saw in XCode 5.1 DP and iOS 7.1 beta, Apple tried to fix this problem. Now separator is shown as needed sometimes below the last row, after some refreshing, but not after tableview creation.

    30 Solutions Collect From Internet About “UITableViewCell Separator disappearing in iOS7”

    I dumped the subview hierarchy of affected cells and found that the _UITableViewCellSeparatorView was set to hidden. No wonder it’s not shown!

    I overrode layoutSubviews in my UITableViewCell subclass and now the separators are displayed reliably:

    Objective-C:

    - (void)layoutSubviews {
        [super layoutSubviews];
    
        for (UIView *subview in self.contentView.superview.subviews) {
            if ([NSStringFromClass(subview.class) hasSuffix:@"SeparatorView"]) {
                subview.hidden = NO;
            }
        }
    }
    

    Swift:

    override func layoutSubviews() {
        super.layoutSubviews()
    
        guard let superview = contentView.superview else {
            return
        }
        for subview in superview.subviews {
            if String(subview.dynamicType).hasSuffix("SeparatorView") {
                subview.hidden = false
            }
        }
    }
    

    The other solutions proposed here didn’t work consistently for me or seem clunky (adding custom 1 px footer views).

    This worked for me:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
        // fix for separators bug in iOS 7
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
    

    I also had the problem with missing separator and I found out that the problem only occured when heightForRowAtIndexPath was returning a decimal number. Solution:

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return ceil(yourHeight) // Ceiling this value fixes disappearing separators
    }
    

    Did you try adding a UIView of height 1 in the header and the footer of the table with light gray background color? Basically it’ll mock the first and last separators.

    @samvermette

    I fixed the problem by using this delegate methods. Now it doesn’t flicker:

    -(void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
        // fix for separators bug in iOS 7
        tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
    }
    
    
    -(void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath {
        // fix for separators bug in iOS 7
        tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
    }
    

    We encountered this issue in our app. When the user selected a cell, a new table view was pushed onto the navigation controller stack and then when the user popped it off, the separator was missing. We solved it by putting [self.tableView deselectRowAtIndexPath:indexPath animated:NO]; in the didSelectRowAtIndexPath table view delegate method.

    Here is an easier (although a bit messy) workaround if you need one, try selecting and deselecting the cell after the data is reloaded and the default animation is done.

    just add:

    [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
    

    I’ve found that the easiest solution is to, after reloading a cell, also reload the cell above:

    if (indexPath.row > 0) {
        NSIndexPath *path = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section];
        [self.tableView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationNone];
    }
    

    A simple and clean solution that works on both iOS 8 and iOS 9 (beta 1)

    Here is a simple, clean and non-intrusive workaround. It involves calling a category method that will fix the separators.

    All you need to do is to walk down the hierarchy of the cell and un-hide the separator. Like this:

    for (UIView *subview in cell.contentView.superview.subviews) {
        if ([NSStringFromClass(subview.class) hasSuffix:@"SeparatorView"]) {
            subview.hidden = NO;
        }
    }
    

    What I recommend is to add this to a category on UITableViewCell, like this:

    @interface UITableViewCell (fixSeparator)
    - (void)fixSeparator;
    @end
    
    @implementation UITableViewCell (fixSeparator)
    
    - (void)fixSeparator {
        for (UIView *subview in self.contentView.superview.subviews) {
            if ([NSStringFromClass(subview.class) hasSuffix:@"SeparatorView"]) {
                subview.hidden = NO;
            }
        }
    }
    
    @end
    

    Because the separator can disappear in different cell than the one currently selected, it’s probably a good idea to call this fix on all cells in the table view. For that, you can add a category to UITableView that goes like this:

    @implementation UITableView (fixSeparators)
    
    - (void)fixSeparators {
        for (UITableViewCell *cell in self.visibleCells) {
            [cell fixSeparator];
        }
    }
    
    @end
    

    With this in place, you can call -fixSeparatos on your tableView right after the action that causes them to disappear. In my case, it was after calling [tableView beginUpdates] and [tableView endUpdates].

    As I stated at the beginning, I’ve tested this on both iOS 8 and iOS 9. I presume it will work even on iOS 7 but I don’t have a way to try it there. As you are probably aware, this does fiddle with internals of the cell so it could stop working in some future release. And Apple could theoretically (0.001% chance) reject your app because of this, but I can’t see how they could even find out what you are doing there (checking the suffix of a class can’t be detected by static analyzers as something bad, IMO).

    Based on @ortwin-gentz’s comment, this solution works for me in iOS 9.

    func fixCellsSeparator() {
    
        // Loop through every cell in the tableview
        for cell: UITableViewCell in self.tableView.visibleCells {
    
            // Loop through every subview in the cell which its class is kind of SeparatorView
            for subview: UIView in (cell.contentView.superview?.subviews)!
                where NSStringFromClass(subview.classForCoder).hasSuffix("SeparatorView") {
                    subview.hidden = false
            }
        }
    
    }
    

    (Swift code)

    I use fixCellsSeparator() function after calling endUpdates() in some methods of my tableView, for example:

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    
        //  Perform some stuff
        //  ...
    
        self.tableView.endUpdates()
        self.fixCellsSeparator()
    
    }
    

    I hope this solution will be helpfull for someone!

    Complement to the answer of airpaulg.

    So basically one has to implement two UITableDelegate methods. Here’s my solution which works on both iOS7 and iOS6.

    #define IS_OS_VERSION_7 (NSFoundationVersionNumber_iOS_6_1 < floor(NSFoundationVersionNumber))
    
    #define UIColorFromRGB(hexRGBValue) [UIColor colorWithRed:((float)((hexRGBValue & 0xFF0000) >> 16))/255.0 green:((float)((hexRGBValue & 0xFF00) >> 8))/255.0 blue:((float)(hexRGBValue & 0xFF))/255.0 alpha:1.0]
    

    // This will hide the empty table grid below the cells if they do not cover the entire screen

    - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
    {
        UIView *view = nil;
    
        if (IS_OS_VERSION_7 /* && <is this the last section of the data source> */)
        {
            CGFloat height = 1 / [UIScreen mainScreen].scale;
            view = [[UIView alloc] initWithFrame:CGRectMake(0., 0., 320., height)];
            view.backgroundColor = UIColorFromRGB(0xC8C7CC);
            view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        }
        else
        {
            view = [UIView new];
        }
        return view;
    }
    
    
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
    {
        if (IS_OS_VERSION_7 /* && <is this the last section of the data source> */)
        {
            return 1 / [UIScreen mainScreen].scale;
        }
        else
        {
            // This will hide the empty table grid below the cells if they do not cover the entire screen
            return 0.01f;
        }
    }
    

    This fixed the issue for me:

    Make sure clipsToBounds is set to YES for the cell, but NO for the cell’s contentView. Also set cell.contentView.backgroundColor = [UIColor clearColor];

    Seems like this issue manifests under so many circumstances.

    For me it had something to do with cell selection. I have no idea why and didn’t have time to dig into it too deep, but I can say it started occurring when I set the cell’s selectionStyle to none. ie:

    //This line brought up the issue for me
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    

    I tried using some of the delegate methods above that toggle on and off the separatorStyle property of the tableView but they didn’t seem to do anything to fix my issue.

    The whole reason why I needed that was because I didn’t even need cell selection.

    So, I did find something that worked for me. I just disabled selection on the UITableView:

    tableView.allowsSelection = NO;
    

    Hope this helps someone, if you don’t need to have cell selection.

    I tried so many suggestions to fix but cannot fix that.Finally I decided to custom separator line as below:

    func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        var lineView = UIView(frame: CGRectMake(20, cell.contentView.frame.size.height - 1.0, cell.contentView.frame.size.width - 20, 1))
    
        lineView.backgroundColor = UIColor(red: 170.0/255.0, green: 170.0/255.0, blue: 170.0/255.0, alpha: 1)
        cell.contentView.addSubview(lineView)
    }
    

    P/S: custom at willDisplayCell NOT cellForRowAtIndexPath

    Quite surprisingly, changing cell’s separatorInset value back and forth seems to be working:

    NSIndexPath *selectedPath = [self.controller.tableView indexPathForSelectedRow];
    [self.controller.tableView deselectRowAtIndexPath:selectedPath animated:YES];
    
    UITableViewCell *cell = [self.controller.tableView cellForRowAtIndexPath:selectedPath];
    UIEdgeInsets insets = cell.separatorInset;
    cell.separatorInset = UIEdgeInsetsMake(0.0, insets.left + 1.0, 0.0, 0.0);
    cell.separatorInset = insets;
    

    I also had the problem of this inconsistent separator line display at the bottom of my UITableView.
    Using a custom footer view, I have been able to create a similar line and to display it on top of the potential existing one (which was sometimes missing).

    The only problem of this solution is that Apple may change one day the thickness of the line

    - (UIView*) tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
    {
        float w = tableView.frame.size.width;
    
        UIView * footerView =  [[UIView alloc] initWithFrame:CGRectMake(0, 0, w, 1)];
        footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        footerView.clipsToBounds=NO;
    
        UIView* separatoraddon = [[UIView alloc] initWithFrame:CGRectMake(0, -.5, w, .5)];
        separatoraddon.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        separatoraddon.backgroundColor = tableView.separatorColor;
        [footerView addSubview:separatoraddon];
    
        return footerView;
    }
    - (CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
    {
        return 1;
    }
    

    I’ve resolved putting these lines of code where the update of the tableview hapens:

    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine;
    

    For example, in my case i’ve putted them here:

    tableView.beginUpdates()
    tableView.insertRowsAtIndexPaths(insertIndexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
    tableView.endUpdates()
    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine;
    

    You need to remove a cell selection before you doing cell’s update. Then you could restore selection.

    NSIndexPath *selectedPath = [self.tableview indexPathForSelectedRow];
        [self.tableview deselectRowAtIndexPath:selectedPath animated:NO];
        [self.tableview reloadRowsAtIndexPaths:@[ path ] withRowAnimation:UITableViewRowAnimationNone];
        [self.tableview selectRowAtIndexPath:selectedPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    

    To fix the above issue, add empty footer in viewDidLoad.

    UIView *emptyView_ = [[UIView alloc] initWithFrame:CGRectZero];   
    emptyView_.backgroundColor = [UIColor clearColor];  
    [tableView setTableFooterView:emptyView_];
    

    Please don’t use the above lines in viewForFooterInSection delegate method. Means Dont implement viewForFooterInSection method.

    to expand on airpaulg answer, because it didn’t entirely work for me..

    i had to also implement the heightforfooter method to get the height

    then i noticed the lightgraycolor is too dark. i fixed this by grabbing the current separator color of the tableview and using that:

    UIColor *separatorGray = [self.briefcaseTableView separatorColor];
    [footerSeparator setBackgroundColor:separatorGray];

    I also ran into this problem in our project. My table view had a tableFooterView which could make this happen. I found the separator below the last row would appear if I removed that tableFooterView.

    What made the difference for me was reloading the row for which the bottom separator line would not appear:

    NSIndexPath *indexPath = 
         [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];  
    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    

    I solve this problem in another way: add a layer that its height is 0.5px and its color is lightgray into tableview.tableFooterView as its sublayer.

    the code is just like this:

    UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 70)];
    CALayer *topSeperatorLine = [CALayer layer];
    topSeperatorLine.borderWidth = 0.5f;
    topSeperatorLine.borderColor = [UIColor lightGrayColor].CGColor;
    topSeperatorLine.frame = CGRectMake(0, 0, 320, 0.5f);
    [tableFooterView.layer addSublayer:topSeperatorLine];
    self.tableView.tableFooterView = tableFooterView;
    

    In your UITableViewCell Subclass implement layoutSubviews and add:

    - (void)layoutSubviews{
        [super layoutSubviews]
        for (UIView *subview in self.contentView.superview.subviews) {
            if ([NSStringFromClass(subview.class) hasSuffix:@"SeparatorView"]) {
                CGRect separatorFrame = subview.frame;
                separatorFrame.size.width = self.frame.size.width;
                subview.frame = separatorFrame;
            }
        }
    }
    

    As someone else mentioned, this problem seems to manifest in a variety of ways. I resolved my issue via the following workaround:

    [tableView beginUpdates];
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    [tableView endUpdates];
    

    Going through the answers and solutions I came up with such observations:

    This seems to be problematic in both iOS 7 and 8. I noticed the problem when selecting cell from code in viewDidLoad method so:

    1) workaround was doing so in viewDidAppear: if someone doesn’t mind the noticeable delay between presenting the view and selecting the cell

    2) the second solution worked for me but the code looks a bit fragile as it base on the internal implementation of the UITableViewCell

    3) adding own separator seems to be the most flexible and best for now but require more coding 🙂

    Setting the style in viewWillAppear worked for me.

    As this is still an issue with IOS 8, I will add my solution in Swift. Set the tableview separator line to none. Then add this code to the cellForRowAtIndexPath delegate method. It will add a nice separator. The if statement allows to decide which cells should have a separator.

        var separator:UIView!
        if let s = cell.viewWithTag(1000)
        {
            separator = s
        }
        else
        {
            separator = UIView()
            separator.tag = 1000
            separator.setTranslatesAutoresizingMaskIntoConstraints(false)
            cell.addSubview(separator)
    
            // Swiper constraints
            var leadingConstraint = NSLayoutConstraint(item: separator, attribute: .Leading, relatedBy: .Equal, toItem: cell, attribute: .Leading, multiplier: 1, constant: 15)
            var heightConstraint = NSLayoutConstraint(item: separator, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0.5)
            var bottomConstraint = NSLayoutConstraint(item: cell, attribute: .Bottom, relatedBy: .Equal, toItem: separator, attribute: .Bottom, multiplier: 1, constant:0)
            var trailingConstraint = NSLayoutConstraint(item: cell, attribute: .Trailing, relatedBy: .Equal, toItem: separator, attribute: .Trailing, multiplier: 1, constant: 15)
            cell.addConstraints([bottomConstraint, leadingConstraint, heightConstraint, trailingConstraint])
        }
    
        if indexPath.row == 3
        {
            separator.backgroundColor = UIColor.clearColor()
        }
        else
        {
            separator.backgroundColor = UIColor.blackColor()
        }
    

    Here’s my solution to this problem. After you insert your cell(s), reload the section. If reloading the entire section is too intensive for you, just reload the indexPaths above and below.

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        //  Fix for issue where seperators disappear
    
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
    }];
    
    [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
    
    [CATransaction commit];
    

    ran into a similar problem, I found that another good solution, especially if your dataSource is not large is reload the tableData when you implement the -(void)scrollViewDidScroll:(UIScrollView *)scrollView method, here’s an example:

    -(void)scrollViewDidScroll:(UIScrollView *)scrollView {
          if (scrollView == self.tableView1) {
              [self.tableView1 reloadData];
          }
    
          else {
              [self.tableView2 reloadData];
          }
    }
    

    You can also just reload non-visible data based on the visible dataSource, but that requires some more hacking.

    Keep in mind this delegate function falls under the UITableViewDelegate protocol!

    Hope it helps!