Grouped UITableview remove outer separator line
Asked Answered
R

20

97

I have a grouped UITableview which is created programatically. Also I have a cell with xib file populated in tableview programmatically as well. So far so good. But I want to only remove outer separator line. I used below code but this time removed all separator line.

self.tableView.separatorColor = [UIColor clearColor];

this is not good option for my situation. Here is the screenshot what i want to do;

enter image description here

Randellrandene answered 12/3, 2015 at 9:43 Comment(7)
Option one: Remove all default separators and add UIViews in the cell as fake separators. Hopefully there's a better option.Shishko
It is the same approach above. As I said before I only remove outer means top and bottom line not inner line. If i remove all separator in tableview and add new line in custom cell tableview's appearance still same.Randellrandene
Solution is, use TableView as Plain tableview, not Grouped. In Grouped it puts separators on headers and footers.Haitian
@Haitian your solution works, thanks :), but plain tableview still shows separator even if it has nothing.Sightread
@Haitian haha I just find the solution to stop displaying separator lines for plain tableview. stackoverflow.com/a/10771747Sightread
Is that your serious, @iphonic?! Who should that help? li2? When someone uses a grouped tableView there is most likely a good reason for that. Did you ever see that the separators are not the only difference between grouped and plain tableView?Hanser
@JulianF.Weinert is right! A better option would be to use custom UITableviewcell.Habakkuk
A
20

I just worked out a solution, as the cell has contentView which is a UIView, so I think you can just focus on the bottomline of contentView.

Here is my code:

first, you have to make the separator to clear

tableView.separatorColor = UIColor.clear

Second, in the cellForRowAt function:

let bottomBorder = CALayer()

bottomBorder.frame = CGRect(x: 0.0, y: 43.0, width: cell.contentView.frame.size.width, height: 1.0)
bottomBorder.backgroundColor = UIColor(white: 0.8, alpha: 1.0).cgColor
cell.contentView.layer.addSublayer(bottomBorder)

here you will see the UI like this:

enter image description here

Autotype answered 19/1, 2018 at 17:47 Comment(0)
S
27

You can remove separators even in grouped UITableView with Static Cell:

class CustomCell: UITableViewCell {

override func layoutSubviews() {
    super.layoutSubviews()
    for view in subviews where view != contentView {
        view.removeFromSuperview()
    }
}
Sybilla answered 14/6, 2016 at 8:21 Comment(1)
But in iOS10 will remove line between cells in one section too.Enterprise
A
24

Here's my solution:

self.tableView.separatorColor = self.tableView.backgroundColor

@xxtesaxx, you should have a real test

Archle answered 4/5, 2017 at 15:36 Comment(3)
@Archle It is going to remove all separator. If you read my question I said I only wanted to remove "outer" lines.Randellrandene
@serhatsezer Yes I read your question, this is a trick solution, it makes outer lines "disappear" and keep separator lines visible.Archle
@Archle But problem will occur if tableView Background color is different than separator Color or TableView background has same color as of cells.. let's say white..Frederiksberg
A
20

I just worked out a solution, as the cell has contentView which is a UIView, so I think you can just focus on the bottomline of contentView.

Here is my code:

first, you have to make the separator to clear

tableView.separatorColor = UIColor.clear

Second, in the cellForRowAt function:

let bottomBorder = CALayer()

bottomBorder.frame = CGRect(x: 0.0, y: 43.0, width: cell.contentView.frame.size.width, height: 1.0)
bottomBorder.backgroundColor = UIColor(white: 0.8, alpha: 1.0).cgColor
cell.contentView.layer.addSublayer(bottomBorder)

here you will see the UI like this:

enter image description here

Autotype answered 19/1, 2018 at 17:47 Comment(0)
S
20

Google, even in 2018, is serving this page as the top result for this question. I didn't have any luck in iOS 11 with any of the provided answers, so here's what I came up with:

extension UITableViewCell {
    func removeSectionSeparators() {
        for subview in subviews {
            if subview != contentView && subview.frame.width == frame.width {
                subview.removeFromSuperview()
            }
        }
    }
}

Calling .removeSectionSeparators() on any UITableViewCell instance will now take care of the problem. In my case at least, the section separators are the only ones with the same width as the cell itself (as the other ones are all indented).

The only question left is from where we should call it. You'd think willDisplayCell would be the best choice, but I discovered that the initial call occurs before the separator views themselves are generated, so no dice.

I ended up putting this in my cellForRowAtIndexPath method just before I return a reloaded cell:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MyReusableIdentifier", for: indexPath)

    Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { (timer) in
        cell.removeSectionSeparators()
    }

    return cell
}

It doesn't feel that elegant, but I haven't run into any issues yet.

EDIT: Looks like we need this too (for reused cells):

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.removeSectionSeparators()
}

Here's a before/after in screenshots with this code:

Before Before Separator Removal

After After Separator Removal

Snocat answered 1/2, 2018 at 3:37 Comment(4)
Works like a charm, thanks. Works even better if you call it in layout subviews for your custom classByrle
This is the only thing I could find that would work for iOS 11 onward. Thanks.Desideratum
I try to put this call in willDisplay or cellForRowAt, both not worked on my side. I'm using iOS 14 and Swift5.Derivative
Good stuff! I put it in override func layoutSubviews() { ... }. But don't forget to call super ;)Softwood
E
14

For removing the top and bottom part of separator line for each section. Add this to your static cell.

override func layoutSubviews() {
    super.layoutSubviews()

    //Get the width of tableview
    let width = subviews[0].frame.width

    for view in subviews where view != contentView {
        //for top and bottom separator will be same width with the tableview width
        //so we check at here and remove accordingly
        if view.frame.width == width {
            view.removeFromSuperview()
        }
    }
}

Result as below image

enter image description here

Eerie answered 19/2, 2019 at 7:47 Comment(3)
Perfect answer but can we achieve this for non-custom cells?Individualize
This was the only solution that worked for me. Thanks!Gleich
Exactly what I was looking for! Thanks!Jeaninejeanlouis
S
11

After inspecting the view hierarchy, it seems each UITableViewCell has only three subviews: the content view (UITableViewCellContentView), and two separator views (_UITableViewCellSeparatorView). I'm not a fan of dynamic class instantiation from NSStrings (and neither is Apple 😉). However, because the contentView of a UITableViewCell is accessible without using private APIs, the solution turns out to be pretty easy.

The idea is just to iterate through the subviews of your UITableViewCell, and remove any views that aren't the contentView:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    let subviews = cell.subviews
    if subviews.count >= 3 {
        for subview in subviews {
            if subview != cell.contentView {
                subview.removeFromSuperview()
                break
            }
        }
    }
}

tableView(:willDisplayCell:forRowAtIndexPath:) is called multiple times during table view rendering, so the above keeps track of state by checking how many subviews the cell has. If it has three subviews, both separators are still intact. It also only removes one of the separators, but you can remove both by removing the break. You can also specify whether to remove the top separator or the bottom separator by checking the subview's frame. If the frame's y-axis origin is 0, that's the top separator. If it's not 0, it's the bottom.

Hope this helps!

Swift 4, Swift 4.2 Update:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let subviews = cell.subviews
    guard subviews.count >= 3 else {
        return
    }

    for subview in subviews where NSStringFromClass(subview.classForCoder) == "_UITableViewCellSeparatorView" {
        subview.removeFromSuperview()
    }
}
Stupa answered 4/3, 2016 at 20:23 Comment(5)
Thank you but this approach may be problem with future iOS versions?Randellrandene
Most likely not, unless Apple changes the internal structure of tableviews to have multiple content views. If you want to be even more careful, you could alternatively use NSStringFromClass and compare those values against "_UITableViewCellSeparatorView" and "UITableViewCellContentView" and then remove.Stupa
Apple did change the tableViewCell view hierarchy from iOS 7 to iOS 8, it really burned me on a project because I used code like this, so I would recommend not doing it that way.Gonfanon
If you're concerned about deprecation or internal changes, I'd recommend using the #available attribute with an empty body for any version of iOS greater than the current version.Stupa
This does no longer work. The separators are not in the view hierarchy when this is called. They seem to get added later on.Slavonic
S
10

iOS 14, Swift 5

In the custom cell class:

override func layoutSubviews(){
    super.layoutSubviews()
    
    for subview in subviews where (subview != contentView && abs(subview.frame.width - frame.width) <= 0.1 && subview.frame.height < 2) {
        subview.removeFromSuperview()                           //option #1 -- remove the line completely
        //subview.frame = subview.frame.insetBy(dx: 16, dy: 0)  //option #2 -- modify the length
    }
}

I want to thank @cook for this solution, as I built on top of it. I had some issues with their solution:

  1. it was removing the default highlight/selected background view, so I added an extra check on the subview's height.
  2. I put my code in layoutSubviews() and haven't had a single issue.
  3. I implemented an approximation between two CGFloats instead of using the equality operator ==, which sounds error-prone to me.

I added the "option #2" in the code, as that's the solution I was personally looking for (I wanted to maintain the separator, but I wanted it to be at the same indentation level as the regular cell separators, in my case a value of 16).

Spalding answered 20/2, 2020 at 21:1 Comment(4)
Finally, I get a worked answer! For iOS 14, Swift 5.Derivative
I get the error Cannot find 'contentView' in scope with this solution. I'm on iOS 15 though.Propane
Doesn't work if I just remove that condition either.Propane
Well I added the contentView conditional to make sure the contentView wasn’t affected at all. Sure, if your comparison values are really small like mine are then it’s probably redundant. My guess is that they changed the nomenclature or hierarchy for contentView in iOS 15Spalding
A
8

That is a really old question, still it's one of the first entries on google when searching for how to remove the top and bottom separators for each section.

After some investigation, I found out that there is no way and just no intention from Apple to make this somehow happen without stupidly complicated hacks of the view hierarchy.

Fortunately there is a absolutely simple and easy way of achieving such a look:

enter image description here

I say simple but for a beginner, this might be difficult because of a lack of understanding how UITableView works and how to implement your own cells. Let me try to explain it:

  1. Create a UITableViewCell subclass
  2. In your cell, create a @IBOutlet weak var separatorView: UIView! property
  3. In your Storyboard, select the tableview cell and select your own cell as the class which backs up the cell. Note: You do not have to use custom style. You still can use Basic, Subtitle, etc.
  4. Set your UITableViews Separator Style to None to hide the default separator
  5. Drag a UIView onto your cell and resize it so it is on the bottom (or the top) of the cell. Use the Size Inspectors Autoresizing to pin it to start/end/bottom and give it a flex width (or Autolayout but thats just over the top in this case)
  6. Connect the view to the outlet of your cell class
  7. In your UITableViewDataSources cellForRowAtIndexPath: set the isHidden property of your custom separator based on if the indexPath.row is the last row (or the first, if your view is at the top) in the section

Heres some example code:

class MyCell: UITableViewCell {
    @IBOutlet weak var separatorView: UIView!
}

class ViewController: UITableViewController {


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCell
        cell.separatorView.isHidden = indexPath.row == 2
        return cell
    }

}

And here some screenshots:

enter image description here

enter image description here

enter image description here

enter image description here

Yes, it is some work but there is just no way to something for free when coding. In the end, we are programmers and it is up to us to do the coding. It's always better to spend the 5-10 minutes setting this up than just copy pasting some hacky code which might not continue to work in the future when Apple decides to change the view hierarchy.

It was actually more work to write this answer than implementing the separator. I as well was searching for a easy and quick solution but in the end there just wasn't a good enough one which I felt was worth using.

I hope you also feel skeptical when you see for-loops iterating over subviews of cells to hide or even remove views at runtime from the view hierarchy Apple provides you, when there is an easy, versatile, stable and future proof solution right around the corner. 7 little steps is really all you need and they are easy to understand.

Almira answered 21/5, 2017 at 3:30 Comment(4)
Does your solution work with accessory view or standard UITableViewCell accessories (chevrons, info buttons, etc.)?Bravin
Since you can use default styles and are not forced to use custom styles, I think it should work without a problem to use the default accessories.Almira
I tried it and couldn't make the separator to appear under chevron in interface builder. Didn't try on device though. The reason for it - xcode allows to add views only to cell's contentView. Chevron is a part of accessory view, which is shown beside contentView.Bravin
In that case you have multiple options. You can simply extend the separator view to the right so it is partly not visible to begin with. When the content view is then pushed to the left, the previous not visible part will now be visible. Not my favorite but it works. Then you could use autolayout in combindation with a custom tableview cell class. Pin the seprartor to the bottom and leading of the content view but the trailing of the cell itself. This way, even when the content view is contracted, the separator will extend all the way to the end.Almira
F
4

This is what I finally came up with:

enter image description here

I was not able to find some better way than this. But it is working for me greatly. Last tried with Xcode Version 7.3.1 (7D1014). This procedure was done through storyboard.

Basically I add a UIView of 0.5 pt Height on the UITableViewCell and then set a background color for that UIView. Set parent UITableView's Separator as None.

Here is the details:

Considering you already set your UITableViewCell, custom or default. On the very first stage set the UITableView's separator as None.

enter image description here

Next add a UIView of 1 pt Height and set the Background as you need, on my case it is Red.

enter image description here

enter image description here

Start setting the constraints. The problem is to set the height of the UIView as 0.5 pt. This is the only Problematic issue for this workflow.

UIView with 0.5 pt Height:

Sharing the way to set 0.5 pt height of the UIView.

First(1) pin the view and then(2) set the height as 0.5. Press Enter.

enter image description here

Finally your UIView will look similar like following.

enter image description here

I was not able to set the height as 0.5 other than this way.

Fallonfallout answered 27/7, 2016 at 18:35 Comment(0)
F
3

Here the solution. This is for static cells. If you want dynamic then just rewrite "count". Hope it helps.

extension NSObject {
   var theClassName: String {
       return    NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
   }
}

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.separatorStyle = .None
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    let count = tableView.numberOfRowsInSection(indexPath.section)
    if ( indexPath.row != count - 1 ) {
        for view in cell.subviews {
            if view.theClassName == "_UITableViewCellSeparatorView" {
                view.backgroundColor = UIColors.redColor()
            }
        }
    }
}
Ferroelectric answered 12/2, 2016 at 22:55 Comment(2)
That looks like a terribly complicated solution. If you have a static tableview, why not set the separator to none and add some custom views with 1p height to the static cells where you want them?Almira
Honestly, I'm not a big fan of the tweak inside the framework of iOS because it can be changed later on. So there may be a problem with the released application.Randellrandene
X
2

Tried various solutions based on the cell.separatorInset = UIEdgeInsetsMake() workaround, none of them working properly.

For my iOS11 UITableViewStyleGrouped based project, this did it:

self.tableView.separatorColor = self.tableView.backgroundColor;
Xeric answered 5/10, 2017 at 0:9 Comment(0)
Q
2

Based on cook's answer, but without a timer:

override func didAddSubview(_ subview: UIView) {
    super.didAddSubview(subview)
    if NSStringFromClass(subview.classForCoder) != "UITableViewCellContentView" && subview.frame.width == frame.width {
        subview.removeFromSuperview()
    }
}

Just add this in a cell's subclass and you don't need anything else. Works on iOS 12.

Queue answered 1/10, 2018 at 13:15 Comment(0)
M
1

I had a similar issue, where I had a Grouped UITableView with custom cells, all designed with Interface Build as .xib files. The cells had white backgrounds, removed the default separators, and added my own ones. I also had custom Header Views for each section. I set the height of those Header Views to be 44 (could be anything...). There was a 1 point height view between my sections, which seemed weird. Apparently, the system adds some clear background view between the sections, even if we specify the custom height to be, say 44, and the custom Header View we return has a white (or some concrete background color). The color we see behind that clear view is the color of of the Table View's background actually. In my case, both the table views and the cells had to be of white color, and setting the background of table view to be white solved the problem (at least visually, but that's what I wanted anyway). Second solution would be to keep the Table View's style as plain, but implement the UITableView delegate method to return 2 or more sections, and also create custom headers if you need to. But that will make header views to stick to the top for a while (while scrolling), until the next section's header view gets closer to it, and then it starts going up too (and that may not be what you really want, but there may be an easy way to fix that, not sure though).

Malinowski answered 16/12, 2017 at 3:26 Comment(0)
T
1

For a single-cell section, simply overriding layoutSubviews and leaving it empty does the trick! https://mcmap.net/q/219065/-how-to-remove-uitableview-section-header-separator

Topdress answered 14/1, 2020 at 12:20 Comment(0)
M
0

Here's a Swift solution that works on iOS 8 and 9.

Define a protocol with a default implementation:

protocol CellSeparatorRemovable { }

extension CellSeparatorRemovable {
    func removeSeparatorLinesFromCell(cell: UITableViewCell, section: Int, row: Int, indexPath: NSIndexPath) {
        guard (section, row) == (indexPath.section, indexPath.row) else { return }

        for view in cell.subviews where view != cell.contentView {
            view.removeFromSuperview()
        }
    }
}

Then, wherever you want to use it, conform to the CellSeparatorRemovable protocol and call its method from …willDisplayCell…:

class SomeVC: UITableViewController, CellSeparatorRemovable {
    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        removeSeparatorLinesFromCell(cell, section: 1, row: 1, indexPath: indexPath)
    }
}

This is a minimal solution; you may need to refactor it if you're dealing with many cells to avoid excessive recursion and/or cell reuse issues.

Motherhood answered 16/4, 2016 at 5:38 Comment(4)
Hello @Aaron I tried your solution but the separator only disappears once the cell is redrawn (when you scroll and it goes out of the screen and then you scroll back), but the first time the screen is loaded the separator is there. I tried cell.setNeedsLayout() and cell.setNeedsDisplay(). ANy idea how to solve it?Epexegesis
At the end I had to subclass UITableViewCell and override layoutSubviews()Epexegesis
hacking the view hierarchy is probably not a good solution. It might change at any time. Also there is no need for custom drawing. Please check out my answer on how to solve the OPs problem the better wayAlmira
I put the code in UITableViewCell's layoutSubview() with small changes and it worked. Thanks for the quick solution!Laugh
H
0

You can access the view using

override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {

    let header = view as! UITableViewHeaderFooterView 
    header.backgroundView . .....
Hardened answered 25/1, 2018 at 14:46 Comment(0)
P
0

Try this it will remove top separator line.

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(indexPath.row == 0 && indexPath.section == 0) {
        for (UIView *view in  cell.subviews) {
            if ([NSStringFromClass([view class]) containsString:@"CellSeparator"]) {
                if (view.frame.origin.y == 0 && CGRectGetHeight(view.frame) < 1.01 ) { //Hide first UITableViewCellSeparatorView
                    NSLog(@"%s Remove View From Cell[Section:0 Row:0] for top border [%@]:%@",__FUNCTION__,NSStringFromClass([view class]),view);
                    view.hidden = YES; //Hide
                }
            }
        }
    }
}
Pescara answered 28/12, 2018 at 10:15 Comment(0)
E
0

iOS 10~13 only remove section head foot line.

-(void)layoutSubviews{
    [super layoutSubviews];
    //for iOS10~iOS13: only remove section head foot line
    for (UIView * v in self.subviews) {
        if ( v != self.contentView &&
            (v.frame.size.width == self.frame.size.width)){
            [v removeFromSuperview];
        }
    }
}

if wan to remove all line:

    for (UIView * v in self.subviews) {
        if (v != self.contentView){
            [v removeFromSuperview];
        }
    }
Enterprise answered 30/4, 2020 at 10:25 Comment(0)
D
0
 override func layoutSubviews() {
    super.layoutSubviews()
    subviews.filter { $0 != contentView && $0.frame.width == frame.width }.first?.removeFromSuperview()
}
Downswing answered 6/10, 2020 at 14:58 Comment(1)
Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes.Domenicadomenico
S
0
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = groupTable.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as! GroupNameCell
    if indexPath.row == 2 /*user your array.count-1 (I have not used array soused 0 here)*/ {
        cell.separatorInset = UIEdgeInsets(top: .zero, left: .zero, bottom: .zero, right: .greatestFiniteMagnitude)
    }
    return cell
}
Saporific answered 17/9, 2022 at 6:38 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Lilithe

© 2022 - 2024 — McMap. All rights reserved.