如何通过单击根据其内容更改表格单元格的高度(折叠和展开)?

Posted

技术标签:

【中文标题】如何通过单击根据其内容更改表格单元格的高度(折叠和展开)?【英文标题】:How to change table cell height (collapse and expand) according to its content by clicking? 【发布时间】:2020-05-18 19:07:34 【问题描述】:

最初用户会看到这样的单元格(只有黑色区域。描述被隐藏)。也就是说,一个单元格在描述之前是可见的。

我希望在单击单元格后,它的高度增加到单元格的末尾,像这样。

标题和描述都不是静态的。它们的大小取决于内容。

您可以看到,在这种情况下,我总是将高度更改为恒定值。这不符合我的要求。

extension MyTableView: UITableViewDataSource, UITableViewDelegate 

    //another funcs...

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 1
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return myDataArray.count
    

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        if selectedRowIndex == indexPath.section 
            return 150  // I want the full cell size to be returned here (cell expanded)
         else 
            return 75  // and here size returned only up to specific view (cell collapsed)
        
    

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        tableView.beginUpdates()
        if selectedRowIndex == indexPath.section 
            selectedRowIndex = -1
         else 
            selectedRowIndex = indexPath.section
        
        tableView.endUpdates()
    

    //another funcs...

【问题讨论】:

请搜索swift UITableViewCell auto height ...您会发现很多很多教程、文章、博客文章、问答等。 MaxB - 删除此问题并再次询问,说明您要展开/折叠单元格。我可以举一个简单的例子。 @DonMag 问题已重新打开 【参考方案1】:

“可扩展”单元有多种方法 - 这一种可能适合您的设计需求...

获得自我调整大小的单元格的常用方法是确保您有一个干净的“自上而下链”约束:

使用这种布局,橙色视图对黑色视图(其父视图)的底部有一个 8 pt 的约束。

为了使这个单元格可展开/可折叠,我们可以添加 另一个 8-pt 约束,这次是从蓝色视图的底部到黑色视图的底部.

最初,我们会遇到约束冲突,因为黑色视图的底部不能是蓝色视图的 8 点橙色视图的 8 点同时。

所以,我们给他们不同的优先级...

如果我们给“blue-bottom”约束一个优先级.defaultHigh (750),给“orange-bottom”一个优先级.defaultLow (250),我们告诉自动布局enforce具有较高优先级的约束并允许较低优先级的约束中断,我们得到:

橙色视图仍然存在,但它现在超出了黑色视图的范围,所以我们看不到它。


这是一个非常简单的例子...

我们为单元格配置两个底部约束 - 一个来自标题标签视图的底部,一个来自描述标签视图的底部。

我们为每个约束设置高优先级或低优先级,具体取决于我们希望单元格展开还是折叠

点击一行将切换其展开状态。

这一切都是通过代码完成的——没有@IBOutlet@IBAction连接——所以只需添加一个新的UITableViewController并将其类分配给TestTableViewController

class MyExpandableCell: UITableViewCell 

    let myImageView: UIImageView = 
        let v = UIImageView()
        v.backgroundColor = UIColor(red: 219.0 / 255.0, green: 59.0 / 255.0, blue: 38.0 / 255.0, alpha: 1.0)
        v.contentMode = .scaleAspectFit
        v.tintColor = .white
        v.layer.cornerRadius = 16.0
        return v
    ()

    let myTitleView: UIView = 
        let v = UIView()
        v.backgroundColor = UIColor(red: 68.0 / 255.0, green: 161.0 / 255.0, blue: 247.0 / 255.0, alpha: 1.0)
        v.layer.cornerRadius = 16.0
        return v
    ()

    let myDescView: UIView = 
        let v = UIView()
        v.backgroundColor = UIColor(red: 243.0 / 255.0, green: 176.0 / 255.0, blue: 61.0 / 255.0, alpha: 1.0)
        v.layer.cornerRadius = 16.0
        return v
    ()

    let myTitleLabel: UILabel = 
        let v = UILabel()
        v.numberOfLines = 0
        v.textAlignment = .center
        v.textColor = .white
        return v
    ()

    let myDescLabel: UILabel = 
        let v = UILabel()
        v.numberOfLines = 0
        v.textColor = .white
        return v
    ()

    let myContainerView: UIView = 
        let v = UIView()
        v.clipsToBounds = true
        v.backgroundColor = .black
        return v
    ()

    var isExpanded: Bool = false 
        didSet 
            expandedConstraint.priority = isExpanded ? .defaultHigh : .defaultLow
            collapsedConstraint.priority = isExpanded ? .defaultLow : .defaultHigh
        
    

    var collapsedConstraint: NSLayoutConstraint!
    var expandedConstraint: NSLayoutConstraint!

    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 

        [myImageView, myTitleView, myDescView, myTitleLabel, myDescLabel, myContainerView].forEach 
            $0.translatesAutoresizingMaskIntoConstraints = false
        

        myTitleView.addSubview(myTitleLabel)
        myDescView.addSubview(myDescLabel)
        myContainerView.addSubview(myTitleView)
        myContainerView.addSubview(myDescView)
        myContainerView.addSubview(myImageView)
        contentView.addSubview(myContainerView)

        let g = contentView.layoutMarginsGuide

        expandedConstraint = myDescView.bottomAnchor.constraint(equalTo: myContainerView.bottomAnchor, constant: -8.0)
        collapsedConstraint = myTitleView.bottomAnchor.constraint(equalTo: myContainerView.bottomAnchor, constant: -8.0)

        expandedConstraint.priority = .defaultLow
        collapsedConstraint.priority = .defaultHigh

        NSLayoutConstraint.activate([

            myTitleLabel.topAnchor.constraint(equalTo: myTitleView.topAnchor, constant: 12.0),
            myTitleLabel.leadingAnchor.constraint(equalTo: myTitleView.leadingAnchor, constant: 8.0),
            myTitleLabel.trailingAnchor.constraint(equalTo: myTitleView.trailingAnchor, constant: -8.0),
            myTitleLabel.bottomAnchor.constraint(equalTo: myTitleView.bottomAnchor, constant: -12.0),

            myDescLabel.topAnchor.constraint(equalTo: myDescView.topAnchor, constant: 12.0),
            myDescLabel.leadingAnchor.constraint(equalTo: myDescView.leadingAnchor, constant: 8.0),
            myDescLabel.trailingAnchor.constraint(equalTo: myDescView.trailingAnchor, constant: -8.0),
            myDescLabel.bottomAnchor.constraint(equalTo: myDescView.bottomAnchor, constant: -12.0),

            myImageView.topAnchor.constraint(equalTo: myContainerView.topAnchor, constant: 8.0),
            myImageView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
            myImageView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),

            myImageView.heightAnchor.constraint(equalToConstant: 80),

            myTitleView.topAnchor.constraint(equalTo: myImageView.bottomAnchor, constant: 8.0),
            myTitleView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
            myTitleView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),

            myDescView.topAnchor.constraint(equalTo: myTitleView.bottomAnchor, constant: 8.0),
            myDescView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
            myDescView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),

            myContainerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            myContainerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            myContainerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            myContainerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),

            expandedConstraint, collapsedConstraint,
        ])


    



class TestTableViewController: UITableViewController 

    let myData: [[String]] = [
        ["Label", "A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label."],
        ["Button", "You can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state."],
        ["Segmented Control", "The segments can represent single or multiple selection, or a list of commands.\n\nEach segment can display text or an image, but not both."],
        ["Text Field", "Displays a rounded rectangle that can contain editable text. When a user taps a text field, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text field can handle the input in an application-specific way. UITextField supports overlay views to display additional information, such as a bookmarks icon. UITextField also provides a clear text control a user taps to erase the contents of the text field."],
        ["Slider", "UISlider displays a horizontal bar, called a track, that represents a range of values. The current value is shown by the position of an indicator, or thumb. A user selects a value by sliding the thumb along the track. You can customize the appearance of both the track and the thumb."],
        ["This cell has a TItle that will wrap onto multiple lines.", "Just to demonstrate that auto-layout is handling text wrapping in the title view."],
    ]

    var rowState: [Bool] = [Bool]()

    override func viewDidLoad() 
        super.viewDidLoad()

        // initialize rowState array to all False (not expanded
        rowState = Array(repeating: false, count: myData.count)

        tableView.register(MyExpandableCell.self, forCellReuseIdentifier: "cell")
    

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return myData.count
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyExpandableCell

        cell.myImageView.image = UIImage(systemName: "\(indexPath.row).circle")
        cell.myTitleLabel.text = myData[indexPath.row][0]
        cell.myDescLabel.text = myData[indexPath.row][1]
        cell.isExpanded = rowState[indexPath.row]

        cell.selectionStyle = .none

        return cell
    

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    guard let c = tableView.cellForRow(at: indexPath) as? MyExpandableCell else 
        return
    
    rowState[indexPath.row].toggle()
    tableView.performBatchUpdates(
        c.isExpanded = rowState[indexPath.row]
    , completion: nil)



结果:

并且,在点击并滚动一下之后:

【讨论】:

我遇到了这个解决方案的问题,例如我的单元格在没有func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat ... 的情况下不会改变它们的高度,它们保持在默认的窄值中。我通过 .xib 文件添加视图,但我以编程方式创建所有约束。问题是否可能是由于 .xib 文件引起的? @MaxB - 是的,问题在于您在 .xib 中设置约束的方式,或加载它的方式。您应该完全实现heightForRowAt @MaxB - 如果你在一个基本项目中有这个(足以证明这个问题),你可以将它发布到 GitHub,我们可以看看。 @MaxB - 我建立了一个 GitHub 存储库,展示了如何使用在 .xib 而不是代码中创建的单元格来执行此操作:github.com/DonMag/AnotherExpandableCellExample 感谢您的帮助,但不幸的是我仍然无法做到这一点。这是我的代码github,我在您的 github 中通过您的示例对其进行了更改,但我无法理解我错过了什么。我的表格视图看起来像 this【参考方案2】:

如果是单元格,将带约束的内容相互依序放置。例如,顶部的单元格视图要距顶部 20 点。中间一个距离您已经配置的顶部元素 30 点。 这样,你放多少内容都没关系。 另外我没看懂,你想点击什么,是按钮吗?如果不是,请使用手势识别器。

【讨论】:

不一定是这种方式。没有按钮,我点击整个单元格,使用func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) ...。我想更改func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat ... 中选定单元格的高度。所以,也许还有更合适的地方。我知道如何更改选定单元格的高度,但问题是如何根据我的要求进行更改。

以上是关于如何通过单击根据其内容更改表格单元格的高度(折叠和展开)?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据标签文本快速更改表格视图单元格高度?

如何在表格视图中更改一个单元格的高度? [复制]

从自定义表格视图单元格类加载时如何调整单元格的高度?

涉及行跨度时,根据内容自动调整 HTML 表格单元格的高度

如何动态更改自定义单元格的表格视图单元格的高度?

如何通过更改单元格的高度来创建动态 TableView 页脚