【问题标题】:Content of UITableViewCell gets clipped upon expanding the cellUITableViewCell 的内容在展开单元格时被剪裁
【发布时间】:2021-01-18 01:58:51
【问题描述】:

我想用UILabel 实现可扩展单元格,当用户点击它时它会增长。我正确设置了约束并在扩展时修改了numberOfLines,以便正确计算大小。 但是,单元格的大小会适当增长,但其内容会被剪掉。当我开始滚动内容时,神奇地出现了。我遵循了一些教程,但我不知道我的错误可能在哪里。请看下面的代码和GIF

编辑:当然,我将UITableView.automaticDimension 作为行高返回

// Label configuration inside cell
    private lazy var label: UILabel = {
        let l = UILabel()
        l.font = .systemFont(ofSize: 14, weight: .regular)
        l.numberOfLines = 3
        l.lineBreakMode = .byTruncatingTail
        return l
    }()

// Modifying this value should correctly resize the label
    var isExpanded: Bool = false {
        didSet {
            label.numberOfLines = isExpanded ? 0 : 3
            setNeedsLayout()
        }
    }

// Setting up constraints. I'm using SnapKit for making the constraints
    func setupView() {
        contentView.addSubview(label)
        label.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.left.equalToSuperview().offset(15)
            make.top.equalToSuperview().offset(4).priority(.high)
        }
    }

这是视图控制器内部管理 tableView 的代码

func didChangeInfoExpanded(at path: IndexPath) {
    DispatchQueue.main.async {
        guard let cell = self.tableView.cellForRow(at: path) as? InfoTableCell else {
            return
        }
        cell.isExpanded.toggle()
        cell.layoutIfNeeded()
        UIView.transition(with: self.tableView, duration: 0.3, options: .transitionCrossDissolve, animations: {
             self.tableView.beginUpdates()
             self.tableView.endUpdates()
        }, completion: nil)
        
        /*
         I have also tried reloading the row but it's made a glitchy animation and the content was still clipped
         self.tableView.reloadRows(at: [path], with: .automatic)
         */
    }
}

【问题讨论】:

  • 从你的动画中很难分辨出什么是什么。都是一个细胞吗?并且您想展开/折叠UILabel?还是那些多个单元格,而您的展开/折叠单元格中只有标签?
  • 它只是一个单元格,它是一个自定义的 UITableViewCell 子类,它被称为 InfoTableCell。该方法中的路径参数是该单元格的 IndexPath。我想扩展为标签,因此将其 numberOfLines 设置为 0
  • 什么触发了didChangeInfoExpanded()?选择单元格时会调用它吗?
  • 是的,点击单元格后调用
  • 嗯...查看您的代码,您在其中使用交叉溶解过渡是否有特定原因?

标签: ios swift autolayout


【解决方案1】:

一个常见的问题是,当我们将行数从零设置为 3 时,标签的文本不会平滑地动画到 3 行……它“捕捉”到 3 行,然后是标签的底部框架和单元格高度动画。视觉效果不是很好。

这是我在这种展开/折叠单元格中获得的最佳结果...

我们添加到单元格的contentView

  • hiddenLabel ... UILabel 将被隐藏
  • container ... UIView 持有可见标签

然后我们添加到容器视图中:

  • visibleLabel ...一个UILabel

两个标签的文本相同。

我们将hiddenLabel 限制在内容视图的所有 4 侧(使用布局边距指南)。当我们改变 hiddenLabel 的行数时,这将决定单元格的高度。

我们还将 container 约束到内容视图的所有 4 个侧面。当内容视图改变高度时,这将改变容器的高度。

在容器内部,我们将 visibleLabel only 限制为 Top / Leading / Trailing... 所以当它的行数设置为零时,它将扩展在容器边界之外(但我们不会看到,因为容器有.clipsToBounds = true)。

这为我们提供了平滑的展开/折叠动画,标签中的文本被“显示”/“覆盖”。

因此,单元格类如下所示:

class ExpandCell: UITableViewCell {
    static let cellID: String = "expandCell"
    
    let container = UIView()
    let visibleLabel = UILabel()
    let hiddenLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        
        [hiddenLabel, visibleLabel, container].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        contentView.addSubview(hiddenLabel)
        contentView.addSubview(container)
        container.addSubview(visibleLabel)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([

            // constrain hiddenLabel Top / Leading / Trailing to contentView
            hiddenLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            hiddenLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            hiddenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            
            // use less than or equal for bottom constraint to avoid auto-layout warnings
            hiddenLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            
            // constrain container Top / Leading / Trailing / Bottom to contentView
            container.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            container.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            container.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            container.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            
            // constrain theLabel Top / Leading / Trailing to container
            visibleLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 0.0),
            visibleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0.0),
            visibleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),
            
            // NO bottom constraint for theLabel
            
        ])
        
        // prevent theLabel from being visible outside the container
        container.clipsToBounds = true
        
        // label properties
        [hiddenLabel, visibleLabel].forEach {
            $0.font = .systemFont(ofSize: 14, weight: .regular)
            $0.numberOfLines = 3
            $0.setContentCompressionResistancePriority(.required, for: .vertical)
            $0.setContentHuggingPriority(.required, for: .vertical)
            $0.contentMode = .top
        }
        // hide the hidden label
        hiddenLabel.isHidden = true

        // during development, so we can easily see frames
        //visibleLabel.backgroundColor = .cyan
        
    }
    func setText(_ str: String, expanded: Bool) -> Void {
        hiddenLabel.text = str
        visibleLabel.text = str
        hiddenLabel.numberOfLines = expanded ? 0 : 3
        visibleLabel.numberOfLines = hiddenLabel.numberOfLines
    }
    func toggleExpanded() -> Bool {
        visibleLabel.numberOfLines = 0
        hiddenLabel.numberOfLines = hiddenLabel.numberOfLines == 0 ? 3 : 0
        return hiddenLabel.numberOfLines == 0
    }
}

cellForRowAt我们设置它(例如):

    if indexPath.row == 1 {
        let c = tableView.dequeueReusableCell(withIdentifier: ExpandCell.cellID, for: indexPath) as! ExpandCell
        // set both hidden and visible label text
        c.setText(detailString)
        c.selectionStyle = .none
        return c
    }

然后,在didSelectRowAt 中,我们可以用动画切换展开/折叠状态:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let c = tableView.cellForRow(at: indexPath) as? ExpandCell {
        tableView.performBatchUpdates({
            c.visibleLabel.numberOfLines = 0
            c.toggleExpanded()
        }, completion: { _ in
            // we need to update the number of lines for the visible label
            //  so we get the ellipses when we're showing the collapsed state
            c.visibleLabel.numberOfLines = c.hiddenLabel.numberOfLines
        })
    }
}

结果:

【讨论】:

    【解决方案2】:

    现在我将稍微修改contentOffset 以模拟滚动,但我很好奇为什么会出现这个问题。

    self.tableView.beginUpdates()
    self.tableView.endUpdates()
    self.tableView.contentOffset.y += 0.2
    

    0.1 不起作用,0.2 是导致内容出现的最小值。万岁 UIKit

    【讨论】:

      猜你喜欢
      • 2018-12-06
      • 1970-01-01
      • 2018-07-28
      • 2015-04-04
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 2013-08-26
      • 1970-01-01
      相关资源
      最近更新 更多