UITableViewCell 使用 separatorInset 隐藏分隔符在 iOS 11 中失败

UITableViewCell hide separator using separatorInset fails in iOS 11

这是我用来在 iOS 11:

之前为单个 UITableViewCell 隐藏分隔符的代码
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (indexPath.row == 0) {


        // Remove separator inset
        if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
            [cell setSeparatorInset:UIEdgeInsetsMake(0, tableView.frame.size.width, 0, 0)];
        }

        // Prevent the cell from inheriting the Table View's margin settings
        if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
            [cell setPreservesSuperviewLayoutMargins:NO];
        }

        // Explictly set your cell's layout margins
        if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
            [cell setLayoutMargins:UIEdgeInsetsMake(0, tableView.frame.size.width, 0, 0)];
        }
    }
}

在此示例中,每个部分的第一行都隐藏了分隔符。我不想完全去掉分隔符 - 只针对某些行。

在iOS11,上面的代码不起作用。单元格的内容完全向右推。

有没有办法在 iOS 11 中完成隐藏单个 UITableViewCell 分隔符的任务?

让我提前澄清一下,我知道我可以使用以下代码隐藏整个 UITableView 的分隔符(希望避免答案指示我这样做):

self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

编辑:还要在下面的评论后澄清,如果我完全包含 setSeparatorInset 行,代码会做同样的事情。所以即使只有那一行,单元格的内容也会被一直推到右边。

首先通过 tableView.separatorStyle = .none 隐藏所有分隔符。然后将您的 UITableViewCell 子类修改为如下内容:

class Cell: UITableViewCell {
    var separatorLine: UIView?
    ...
}

将以下内容添加到 tableView(_:cellForRowAt:) 的方法体中:

if cell.separatorLine == nil {
    // Create the line.
    let singleLine = UIView()
    singleLine.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
    singleLine.translatesAutoresizingMaskIntoConstraints = false

    // Add the line to the cell's content view.
    cell.contentView.addSubview(singleLine)

    let singleLineConstraints = [singleLine.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 8),
                                 singleLine.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor),
                                 singleLine.topAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -1),
                                 singleLine.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: 0)]
    cell.contentView.addConstraints(singleLineConstraints)

    cell.separatorLine = singleLine
}

cell.separatorLine?.isHidden = [Boolean which determines if separator should be displayed]

此代码在 Swift 中,因此请按照您对 Objective-C 翻译的要求进行操作,并确保继续进行版本检查。在我的测试中,我根本不需要使用 tableView(_:willDisplayCell:forRowAt:),而是一切都在 cellForRowAtIndexPath: 方法中。

IMO 的最佳方法是添加一个高度为 1pt 的简单 UIView。 我编写了以下协议,使您能够在您喜欢的任何 UITableViewCell 中使用它:

// Base protocol requirements
protocol SeperatorTableViewCellProtocol: class {
    var seperatorView: UIView! {get set}
    var hideSeperator: Bool! { get set }
    func configureSeperator()
}

// Specify the separator is of a UITableViewCell type and default separator configuration method
extension SeperatorTableViewCellProtocol where Self: UITableViewCell {
    func configureSeperator() {
        hideSeperator = true
        seperatorView = UIView()
        seperatorView.backgroundColor = UIColor(named: .WhiteThree)
        contentView.insertSubview(seperatorView, at: 0)

        // Just constraint seperatorView to contentView
        seperatorView.setConstant(edge: .height, value: 1.0)
        seperatorView.layoutToSuperview(.bottom)
        seperatorView.layoutToSuperview(axis: .horizontally)

        seperatorView.isHidden = hideSeperator
    }
}

你这样使用它:

// Implement the protocol with custom cell
class CustomTableViewCell: UITableViewCell, SeperatorTableViewCellProtocol {

    // MARK: SeperatorTableViewCellProtocol impl'
    var seperatorView: UIView!
    var hideSeperator: Bool! {
        didSet {
            guard let seperatorView = seperatorView else {
                return
            }
            seperatorView.isHidden = hideSeperator
        }
    }


    override func awakeFromNib() {
        super.awakeFromNib()
        configureSeperator()
        hideSeperator = false
    }
}

仅此而已。您可以自定义任何 UITableViewCell 子类以使用分隔符。

通过以下方式设置 tableView:willDisplayCell:forRowAtIndexPath 的分隔符可见性:

cell.hideSeperator = false / true

如果您不想在 UITableViewCell 中添加自定义分隔符,我可以向您展示另一种可供考虑的解决方法。

工作原理

因为分隔符的颜色是在 UITableView 级别上定义的,所以没有明确的方法可以根据 UITableViewCell 实例更改它。它不是 Apple 设计的,您唯一能做的就是破解它。

您首先需要访问分隔符视图。你可以用这个小扩展来做到这一点。

extension UITableViewCell {
    var separatorView: UIView? {
        return subviews .min { [=10=].frame.size.height < .frame.size.height }
    }
}

当您可以访问分隔符视图时,您必须适当地配置您的 UITableView。首先,将所有分隔符的全局颜色设置为.clear(但不要禁用它们!)

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.separatorColor = .clear
}

接下来,为每个单元格设置分隔符颜色。您可以为它们中的每一个设置不同的颜色,取决于您。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "SeparatorCell", for: indexPath)
    cell.separatorView?.backgroundColor = .red

    return cell
}

最后,对于该部分的每一行,将分隔符颜色设置为 .clear

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row == 0 {
        cell.separatorView?.backgroundColor = .clear
    }
}

为什么有效

首先,让我们考虑一下 UITableViewCell 的结构。如果您打印出单元格的 subviews,您将看到以下输出。

<UITableViewCellContentView: 0x7ff77e604f50; frame = (0 0; 328 43.6667); opaque = NO; gestureRecognizers = <NSArray: 0x608000058d50>; layer = <CALayer: 0x60400022a660>>
<_UITableViewCellSeparatorView: 0x7ff77e4010c0; frame = (15 43.5; 360 0.5); layer = <CALayer: 0x608000223740>>
<UIButton: 0x7ff77e403b80; frame = (0 0; 22 22); opaque = NO; layer = <CALayer: 0x608000222500>>

如您所见,有一个包含内容、分隔符和附件按钮的视图。从这个角度来看,您只需要访问分隔符视图并修改它的背景。不幸的是,这并不容易。

让我们在视图调试器中查看相同的 UITableViewCell。如您所见,有 两个 分隔符视图。您需要访问调用 willDisplay: 时不存在的底部。这是第二个 hacky 部分发挥作用的地方。

检查这两个元素时,您会看到第一个(从顶部开始)的背景颜色设置为 nil,第二个的背景颜色设置为您指定的值整个 UITableView。在这种情况下,有颜色的分隔符覆盖没有颜色的分隔符。

要解决这个问题我们必须"reverse"情况。我们可以将所有分隔符的颜色设置为 .clear,这将显示我们有权访问的分隔符。最后,我们可以将可访问分隔符的背景颜色设置为所需的颜色。

实际上,当我使用 UITableView 时,我总是创建自定义单元格 class 和分隔符,并且通常将我自己的分隔符设置为高度为 1 和左右约束的 UIView,在您的情况下,请执行以下步骤: 1. 创建自定义单元格。 2. 添加 UIView 作为分隔符。 3. Link 此分隔符为您的自定义 class。 4. 将 hideSeparator 方法添加到 class.

-(void)hideSeparator{
   self.separator.hidden == YES;
}

5。隐藏您想要的任何单元格的分隔符。

希望能解决您的问题。

这个模式我也学过一次。多年来我调整了它。就在今天,我不得不删除 directionalLayoutMargins 部分才能使其正常工作。现在我的函数看起来像这样:

    func adjustCellSeparatorInsets(at indexPath: IndexPath,
                                   for modelCollection: ModelCollection,
                                   numberOfLastSeparatorsToHide: Int) {

        guard modelCollection.isInBounds(indexPath) else { return }

        let model = modelCollection[indexPath]
        var insets = model.separatorInsets
        let lastSection = modelCollection[modelCollection.sectionCount - 1]
        let shouldHideSeparator = indexPath.section == modelCollection.sectionCount - 1
            && indexPath.row >= lastSection.count - numberOfLastSeparatorsToHide

        // Don't show the separator for the last N rows of the last section
        if shouldHideSeparator {
            insets = NSDirectionalEdgeInsets(top: 0, leading: 9999, bottom: 0, trailing: 0)
        }

        // removing separator inset
        separatorInset = insets.edgeInsets

        // prevent the cell from inheriting the tableView's margin settings
        preservesSuperviewLayoutMargins = false
    }

如果您想在 Github 上查看它,请参阅 this link

可以找到带有解释的删除的 PR here