分组的 UITableview 删除外部分隔线

Grouped UITableview remove outer separator line

我有一个以编程方式创建的分组 UITableview。我也有一个单元格,其中 xib 文件也以编程方式填充在 tableview 中。到目前为止,一切都很好。但我只想删除外部分隔线。我使用了下面的代码,但这次删除了所有分隔线。

self.tableView.separatorColor = [UIColor clearColor];

这对我的情况来说不是一个好的选择。这是我想要做的截图;

这里是解决方案。这适用于静态单元格。如果你想要动态的,那么只需重写 "count"。希望对你有帮助。

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()
            }
        }
    }
}

检查视图层次结构后,每个 UITableViewCell 似乎只有三个子视图:内容视图 (UITableViewCellContentView) 和两个分隔视图 (_UITableViewCellSeparatorView)。我不喜欢 NSStrings 的动态 class 实例化(Apple 也不喜欢)。但是,由于 UITableViewCellcontentView 无需使用私有 API 即可访问,因此解决方案非常简单。

这个想法只是遍历 UITableViewCell 的子视图,并删除任何不是 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:) 在 table 视图渲染期间被多次调用,所以上面通过检查单元格有多少子视图来跟踪状态。如果它有三个子视图,两个分隔符仍然完好无损。它还只删除其中一个分隔符,但您可以通过删除 break 来删除两者。您还可以通过检查子视图的框架来指定是删除顶部分隔符还是底部分隔符。如果框架的 y 轴原点是 0,那就是顶部分隔符。如果不是0,就是底部

希望对您有所帮助!

Swift4,Swift4.2更新:

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()
    }
}

这是适用于 iOS 8 和 9 的 Swift 解决方案。

定义一个带有默认实现的协议:

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()
        }
    }
}

然后,在任何你想使用它的地方,遵守CellSeparatorRemovable协议并从…willDisplayCell…调用它的方法:

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

这是一个最小的解决方案;如果您要处理许多单元格,则可能需要重构它以避免过度递归 and/or 单元格重用问题。

即使在包含静态单元格的分组 UITableView 中,您也可以删除分隔符:

class CustomCell: UITableViewCell {

override func layoutSubviews() {
    super.layoutSubviews()
    for view in subviews where view != contentView {
        view.removeFromSuperview()
    }
}

这是我最终想出的:

我找不到比这更好的方法。但这对我很有用。最后尝试使用 Xcode 版本 7.3.1 (7D1014)。这个过程是通过故事板完成的。

基本上我在 UITableViewCell 上添加了一个高度为 0.5 pt 的 UIView,然后为该 UIView 设置了背景颜色。将父 UITableView 的分隔符设置为 None.

详情如下:

考虑到您已经设置了 UITableViewCell,自定义或默认。在第一阶段将 UITableView 的分隔符设置为 None.

接下来添加一个 1 pt 高度的 UIView 并根据需要设置背景,在我的例子中它是红色。

开始设置约束。问题是将 UIView 的高度设置为 0.5 pt。这是此工作流唯一的问题。

高度为 0.5 pt 的 UIView:

分享设置UIView高度为0.5pt的方法

首先 (1) 固定视图,然后 (2) 将高度设置为 0.5。按 Enter。

最终您的 UIView 将如下所示。

除此之外,我无法将高度设置为 0.5。

这是我的解决方案:

self.tableView.separatorColor = self.tableView.backgroundColor

这是一个非常古老的问题,在搜索如何删除每个部分的顶部和底部分隔符时,它仍然是 google 上的第一个条目之一。

经过一些调查,我发现如果不对视图层次结构进行愚蠢而复杂的黑客攻击,Apple 就没有办法,也没有意图以某种方式实现这一点。

幸运的是,有一种绝对简单易行的方法可以实现这样的外观:

我说的是简单,但对于初学者来说,这可能会很困难,因为不了解 UITableView 的工作原理以及如何实现自己的单元格。让我试着解释一下:

  1. 创建一个UITableViewCell子class
  2. 在您的单元格中,创建一个 @IBOutlet weak var separatorView: UIView! 属性
  3. 在您的 Storyboard 中,select tableview 单元格和 select 您自己的单元格作为备份单元格的 class。 注意:您不必使用自定义样式。您仍然可以使用 Basic、Subtitle 等
  4. 将您的 UITableViews Separator Style 设置为 None 以隐藏默认分隔符
  5. UIView 拖到您的单元格上并调整其大小,使其位于单元格的底部(或顶部)。使用 Size Inspectors Autoresizing 将它固定到 start/end/bottom 并给它一个 flex 宽度(或 Autolayout 但在这种情况下只是在顶部)
  6. 将视图连接到单元格的出口 class
  7. 在您的 UITableViewDataSources cellForRowAtIndexPath: 中,根据 indexPath.row 是否是最后一行(或首先,如果您的视图位于顶部)
  8. 部分

下面是一些示例代码:

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
    }

}

这里是一些截图:

是的,这是一些工作,但在编码时没有办法免费获得一些东西。归根结底,我们是程序员,由我们来进行编码。花 5-10 分钟设置它总是比复制粘贴一些 hacky 代码更好,当 Apple 决定更改视图层次结构时,这些代码将来可能无法继续工作。

写这个答案实际上比实现分隔符要花更多的功夫。我也在寻找一种简单快捷的解决方案,但最终没有一个我觉得值得使用的足够好。

当您看到 for 循环遍历单元格的子视图以在运行时从 Apple 提供给您的视图层次结构中隐藏甚至删除视图时,我希望您也感到怀疑,当有一个简单、通用、稳定和未来的证据时解决方案指日可待。 7 个小步骤就是您真正需要的全部,而且它们很容易理解。

尝试了基于 cell.separatorInset = UIEdgeInsetsMake() 变通方法的各种解决方案,其中 none 工作正常。

对于我的基于 iOS11 UITableViewStyleGrouped 的项目,这是这样做的:

self.tableView.separatorColor = self.tableView.backgroundColor;

我有一个类似的问题,我有一个带有自定义单元格的分组 UITable 视图,所有这些都使用 Interface Build 设计为 .xib 文件。这些单元格有白色背景,删除了默认分隔符,并添加了我自己的分隔符。我还有每个部分的自定义 Header 视图。我将那些 Header 视图的高度设置为 44(可以是任何东西......)。我的部分之间有一个 1 点高度的视图,这看起来很奇怪。显然,系统在部分之间添加了一些清晰的背景视图,即使我们将自定义高度指定为 44,并且我们 return 的自定义 Header 视图具有白色(或一些具体的背景颜色).我们在清晰视图后面看到的颜色实际上是 Table 视图背景的颜色。在我的例子中,table 视图和单元格都必须是白色,并将 table 视图的背景设置为白色解决了这个问题(至少在视觉上,但这就是我想要的).第二种解决方案是将 Table 视图的样式保持为普通,但将 UITableView 委托方法实现到 return 2 个或更多部分,并创建自定义 headers 如果你需要的话。但这将使 header 视图停留在顶部一段时间(滚动时),直到下一部分的 header 视图更接近它,​​然后它也开始上升(并且可能不会是你真正想要的,但可能有一种简单的方法来解决这个问题,但不确定)。

我刚刚想出了一个解决方案,因为单元格有 contentView,这是一个 UIView,所以我认为你可以只关注 contentView 的底线。

这是我的代码:

首先,你必须让分隔符清除

tableView.separatorColor = UIColor.clear

其次,在cellForRowAt函数中:

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)

在这里你会看到这样的UI:

您可以使用

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

    let header = view as! UITableViewHeaderFooterView 
    header.backgroundView . .....

Google,即使在 2018 年,也将该页面作为该问题的最佳结果。我在 iOS 11 中没有任何提供的答案,所以这就是我想出的:

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

在任何 UITableViewCell 实例上调用 .removeSectionSeparators() 现在可以解决这个问题。至少在我的例子中,节分隔符是唯一与单元格本身具有相同宽度的分隔符(因为其他的都是缩进的)。

剩下的唯一问题是我们应该从哪里调用它。你会认为 willDisplayCell 是最好的选择,但我发现初始调用发生在分隔视图本身生成之前,所以没有骰子。

我最终将其放入我的 cellForRowAtIndexPath 方法中,就在我 return 一个重新加载的单元格之前:

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
}

感觉不是那么优雅,但我还没有 运行 解决任何问题。

编辑: 看起来我们也需要这个(对于重复使用的单元格):

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

下面是 before/after 的屏幕截图,代码如下:

之前

之后

根据厨师的回答,但没有计时器:

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

只需将它添加到单元格的子类中,您不需要任何其他东西。适用于 iOS 12.

试试这个,它会删除顶部分隔线。

-(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
                }
            }
        }
    }
}

用于删除每个部分的分隔线的顶部和底部。将此添加到您的静态单元格。

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()
        }
    }
}

结果如下图

对于单单元格部分,只需覆盖 layoutSubviews 并将其保留为 即可!

iOS14,Swift5

在自定义单元格中 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
    }
}

我要感谢@cook 提供的这个解决方案,因为我是在它的基础上构建的。我对他们的解决方案有一些疑问:

  1. 它正在删除默认的 highlight/selected 背景视图,因此我添加了对子视图高度的额外检查。
  2. 我将我的代码放在 layoutSubviews() 中并且没有遇到任何问题。
  3. 我实现了两个 CGFloat 之间的近似值,而不是使用相等运算符 ==,这对我来说听起来容易出错。

我在代码中添加了“选项 #2”,因为这是我个人寻找的解决方案(我想保留分隔符,但我希望它与常规单元格分隔符处于相同的缩进级别,在我的例子中,值为 16).

iOS10~13只去掉节头脚线

-(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];
        }
    }
}

如果要删除所有行:

    for (UIView * v in self.subviews) {
        if (v != self.contentView){
            [v removeFromSuperview];
        }
    }
 override func layoutSubviews() {
    super.layoutSubviews()
    subviews.filter { [=10=] != contentView && [=10=].frame.width == frame.width }.first?.removeFromSuperview()
}