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

Posted

技术标签:

【中文标题】UITableViewCell 使用 separatorInset 隐藏分隔符在 iOS 11 中失败【英文标题】:UITableViewCell hide separator using separatorInset fails in iOS 11 【发布时间】:2017-10-16 22:29:27 【问题描述】:

这是我在 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)];
        
    

在此示例中,每个部分的第一行的分隔符是隐藏的。我不想完全摆脱分隔符 - 仅适用于某些行。

在 iOS 11 中,上述代码不起作用。单元格的内容被完全推到右边。

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

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

self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

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

【问题讨论】:

这可能与您设置布局边距有关。您是否尝试删除该代码块? 如果我完全包含 setSeparatorInset 行,它的作用完全相同。所以即使只有那一行,单元格的内容也会一直推到右边。 @SAHM 你对你的 tableView 或单元格做了什么吗?您的代码在我的模拟器 iPhone8 iOS 11 上完美运行。i.stack.imgur.com/F2WZ5.png 我建议将 separatorStyle 设置为 UITableViewCellSeparatorStyleNone 然后在自定义单元格中添加自定义分隔符,同时在 .h 中提供隐藏和显示方法来隐藏和显示自定义分隔符。这很容易,我总是这样做。 【参考方案1】:

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

工作原理

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

您需要做的第一件事是访问分隔符视图。你可以用这个小扩展来做到这一点。

extension UITableViewCell 
    var separatorView: UIView? 
        return subviews .min  $0.frame.size.height < $1.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 指定的值.在这种情况下,带颜色的分隔符会覆盖不带颜色的分隔符。

为了解决这个问题,我们必须“扭转”局面。我们可以将所有分隔符的颜色设置为.clear,这将发现我们可以访问的那个。最后,我们可以将可访问分隔符的背景颜色设置为所需的颜色。

【讨论】:

哇 - 很好的答案!如果您找到一种使用插图的方法,我仍然会感兴趣,因为有时我需要显示一条完整的线而不是隐藏线,所以我认为总体上具有这种灵活性会很好。我想知道这个在不杀死单元格内容的情况下更改线宽的选项是否仍然可用。但你的回答是这个问题的一个非常聪明的答案。谢谢。【参考方案2】:

首先通过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: 方法中。

【讨论】:

刚刚发布了一个模仿分隔符插入并提供完整单元格级别控制的编辑。如果你想做出更快的实现,你可以使用 CoreGraphics 来做一些绘图,但这并不比 UIKit 实现贵太多。 谢谢大卫,我很感激。不过,如果可能的话,我仍然希望找到一种抑制分隔符的方法。这将是最后的手段。 仍在寻找更好的解决方案。他们更改了 iOS 11 中的布局代码,特别是与边距等有关。这就是为什么它现在被打破了。 我已经在安全区域尝试了几种不同的方法,但我无法得到它。如果我会发布。【参考方案3】:

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

【讨论】:

谢谢丹,我很感激。不过,如果可能的话,我仍然希望找到一种抑制分隔符的方法。这将是最后的手段。 很高兴我能帮上忙。 :-) 如果你最终找到了另一个解决方案,知道会很有趣。【参考方案4】:

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

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

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

希望能解决您的问题。

【讨论】:

【参考方案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。

可以在here找到移除的PR和解释。

【讨论】:

以上是关于UITableViewCell 使用 separatorInset 隐藏分隔符在 iOS 11 中失败的主要内容,如果未能解决你的问题,请参考以下文章

使用 UITextViewDelegate 更新 UITableViewCell 标签

使用 UIAppearance 设置 UITableViewCell 文本样式

使用 UINib 为 UITableViewCell 分配插座

使用笔尖的 UITableViewCell 背景颜色

UITableViewCell 的动态高度

新手教程之使用Xib自定义UITableViewCell