【问题标题】:How to expand and collapse tableview cells with dynamic height programmatically?如何以编程方式展开和折叠具有动态高度的表格视图单元格?
【发布时间】:2019-06-24 10:36:52
【问题描述】:

这是我的问题的视频:https://imgur.com/a/qvGSLfD

我的自定义单元格有 2 个标签:一个主标签和一个副标题标签,这两个标签在各个方向都被限制在单元格的 contentView layoutMarginsGuide 中。

首先,整个展开折叠机制似乎有点笨拙,因为我要激活和停用约束,然后重新加载 indexPaths。

我尝试过这种方法:当我选择单元格时,我会停用只负责设置一个标签的约束并激活两个标签的约束,当它折叠时我会做相反的事情。我觉得这不是一个好方法,但我找不到任何支持动态单元格高度的东西。

基本上,我可以这样做:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.row == selectedRowIndex {
        return 200 //Expanded
    }
    return tableView.rowHeight //Not expanded
}

但我不能只为大小不同的单元格返回相同的数字。

这是我的 viewController 类中的 tableView 代码:

 var tableView: UITableView = {
    let tv = UITableView(frame: .zero)
    tv.register(CustomCell.self, forCellReuseIdentifier: CustomCell.reuseIdentifier())
    tv.translatesAutoresizingMaskIntoConstraints = false
    tv.rowHeight = UITableView.automaticDimension
    tv.estimatedRowHeight = 60.0
    tv.estimatedSectionHeaderHeight = 0
    tv.estimatedSectionFooterHeight = 0
    tv.showsVerticalScrollIndicator = false
    tv.tableFooterView = UIView()
    tv.alwaysBounceVertical = true
    tv.decelerationRate = .fast
    tv.bounces = false
    tv.dataSource = self
    tv.delegate = self
    return tv
}()

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.reuseIdentifier(), for: indexPath) as! CustomCell
    cell.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 99999)
    cell.contentView.bounds = cell.bounds
    cell.layoutIfNeeded()
    cell.wordLabel.preferredMaxLayoutWidth = cell.wordLabel.frame.width
    cell.descriptionLabel.preferredMaxLayoutWidth = cell.descriptionLabel.frame.width

    //customize this later
    cell.backgroundColor = .white
    cell.set(content: datasource[indexPath.row])
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath) as! CustomCell
    cell.toggle()
    tableView.reloadRows(at: [indexPath], with: .automatic)
}

以下是相关的自定义 Cell 函数:

var isExpanded: Bool!

private var singleLabelConstraints: [NSLayoutConstraint]!
private var doubleLabelConstraints: [NSLayoutConstraint]!

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setupLabels()
}

required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupLabels()
}

func toggle() {
    isExpanded = !isExpanded
    if isExpanded == false {
        print("collapsed")
        self.descriptionLabel.isHidden = true
        NSLayoutConstraint.activate(singleLabelConstraints)
        NSLayoutConstraint.deactivate(doubleLabelConstraints)
    } else if isExpanded == true {
        print("expanded")
        self.descriptionLabel.isHidden = false
        NSLayoutConstraint.deactivate(singleLabelConstraints)
        NSLayoutConstraint.activate(doubleLabelConstraints)
    }
}

func setupLabels() {
    isExpanded = false
    descriptionLabel.isHidden = true
    self.contentView.addSubview(wordLabel)
    self.contentView.addSubview(descriptionLabel)

    singleLabelConstraints = [
        wordLabel.leadingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.leadingAnchor,
            constant: labelInsets.left
        ),
        wordLabel.topAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.topAnchor,
            constant: labelInsets.top
        ),
        wordLabel.trailingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.trailingAnchor,
            constant: labelInsets.right
        ),
        wordLabel.bottomAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.bottomAnchor,
            constant: labelInsets.bottom
        )
    ]

    doubleLabelConstraints = [
        wordLabel.leadingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.leadingAnchor,
            constant: labelInsets.left
        ),
        wordLabel.topAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.topAnchor,
            constant: labelInsets.top
        ),
        wordLabel.trailingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.trailingAnchor,
            constant: labelInsets.right
        ),

        descriptionLabel.leadingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.leadingAnchor,
            constant: labelInsets.left
        ),
        descriptionLabel.topAnchor.constraint(
            equalTo: self.wordLabel.bottomAnchor,
            constant: labelInsets.top
        ),
        descriptionLabel.trailingAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.trailingAnchor,
            constant: labelInsets.right
        ),
        descriptionLabel.bottomAnchor.constraint(
            equalTo: self.contentView.layoutMarginsGuide.bottomAnchor,
            constant: labelInsets.bottom
        )
    ]
    NSLayoutConstraint.activate(singleLabelConstraints)
}

我希望过渡更平滑,我想要一些可以轻松切换单元格的东西,同时保持主标签就位,然后只显示副标题标签。

【问题讨论】:

  • 您正在为展开-折叠更新约束,这是一种不好的做法,即使您查看控制台也可能存在约束中断错误。
  • @dahiya_boy 你说得对,我刚结束使用堆栈视图并切换其中一个视图,因为约束不起作用

标签: ios swift uitableview tableview


【解决方案1】:

你好,你可以通过下面的这个链接来解决这个问题,

http://vasundharavision.com/blog/ios/expandable-tableview-without-third-party

我希望它对你有用。

【讨论】:

  • 这也可能是一个选项,但由于我不使用可扩展/可折叠的项目集合,所以它并不理想。除了主标签之外,我只有一个要显示和隐藏的标签。查看已接受的答案。
【解决方案2】:

您可以使用UIStackView 展开和折叠表格视图。选择表格视图单元格时,您可以隐藏和显示描述标签。

class ViewController: UIViewController {
    var tableView: UITableView = {
        let tv = UITableView(frame: .zero)
        tv.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        tv.translatesAutoresizingMaskIntoConstraints = false
        tv.rowHeight = UITableView.automaticDimension
        tv.estimatedRowHeight = 100.0
        tv.estimatedSectionHeaderHeight = 0
        tv.estimatedSectionFooterHeight = 0
        tv.showsVerticalScrollIndicator = false
        tv.tableFooterView = UIView()
        tv.alwaysBounceVertical = true
        tv.decelerationRate = .fast
        tv.bounces = false
        return tv
    }()
    var selectedCell:IndexPath?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: ["tableView":tableView]))
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: ["tableView":tableView]))
    }


}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell ?? CustomCell()
        if let selectedCell = selectedCell, selectedCell == indexPath {
            cell.descriptionLabel.isHidden = false
        } else {
            cell.descriptionLabel.isHidden = true
        }
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedCell = indexPath
        tableView.reloadData()
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }

}

class CustomCell: UITableViewCell {

    let stackView = UIStackView()
    let wordLabel = UILabel()
    let descriptionLabel = UILabel()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupLabels()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupLabels()
    }

    func setupLabels() {

        selectionStyle = .none

        stackView.axis = .vertical
        stackView.distribution = .equalSpacing
        stackView.spacing = 5
        stackView.alignment = .fill
        stackView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(stackView)

        wordLabel.translatesAutoresizingMaskIntoConstraints = false
        wordLabel.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lor"
        wordLabel.numberOfLines = 0
        wordLabel.lineBreakMode = .byWordWrapping
        stackView.addArrangedSubview(wordLabel)

        descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
        descriptionLabel.text = "It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
        descriptionLabel.numberOfLines = 0
        descriptionLabel.lineBreakMode = .byWordWrapping
        stackView.addArrangedSubview(descriptionLabel)

        wordLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true
        descriptionLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true

        stackView.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor,constant: 10).isActive = true
        stackView.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor,constant: 10).isActive = true
        stackView.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor,constant: 10).isActive = true
        stackView.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor,constant: 10).isActive = true

    }
}

【讨论】:

  • 正是我最终所做的,并且效果很好
【解决方案3】:

试试这个:

tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()

【讨论】:

    猜你喜欢
    • 2018-07-31
    • 2016-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    相关资源
    最近更新 更多