Search code examples
iosswiftuitableviewuikitsnapkit

Change Height Constraint for View wrapped in UITableViewCell


I have a UITableView with a custom cell that contains a view.

After the view is displayed, I want to change the hight of the nested view.

Im using the updateConstraints function of the SnapKit framework.

However, I get the error, that the updated constraint conflicts with the UIView-Encapsulated-Layout-Height that apparently comes from the initial set height.

    "<SnapKit.LayoutConstraint:[email protected]#89 SnapKitTest.NestedView:0x102005ef0.height == 100.0>",
"<SnapKit.LayoutConstraint:[email protected]#70 SnapKitTest.NestedView:0x102005ef0.top == SnapKitTest.TestCell:0x10280e200.top>",
"<SnapKit.LayoutConstraint:[email protected]#70 SnapKitTest.NestedView:0x102005ef0.bottom == SnapKitTest.TestCell:0x10280e200.bottom>",
"<NSLayoutConstraint:0x280058a50 'UIView-Encapsulated-Layout-Height' SnapKitTest.TestCell:0x10280e200.height == 20   (active)>"
)

Will attempt to recover by breaking constraint 
<SnapKit.LayoutConstraint:[email protected]#89 SnapKitTest.NestedView:0x102005ef0.height == 100.0>

Below is some source code to run a sample app:

import UIKit
import SnapKit

class ViewController: UIViewController {

    let tableView = UITableView.init(frame: .zero)

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalTo(self.view)
        }

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "X")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 100
        tableView.delegate = self
        tableView.dataSource = self
    }


    override func viewDidAppear(_ animated: Bool) {
        if let c = tableView.cellForRow(at: IndexPath(row: 5, section: 0)) as? TestCell {
            c.nestedView.snp.updateConstraints { make in
                make.height.equalTo(100)
            }
        } else {
            print("didnt geht cell")
        }
    }

}


extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if indexPath.row == 5 {
            let c = TestCell.init(style: .default, reuseIdentifier: nil)

            return c


        }

        let c = tableView.dequeueReusableCell(withIdentifier: "X")
        c?.backgroundColor = .orange
        return c!
    }

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


}


class TestCell: UITableViewCell {
    let nestedView = NestedView()

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


        addSubview(nestedView)
        nestedView.snp.makeConstraints { make in
            make.edges.equalTo(self)
        }
        backgroundColor = .blue

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


class NestedView: UIView {

    init() {
        super.init(frame: .zero)
        backgroundColor = .yellow

        snp.makeConstraints { make in
            make.height.equalTo(20)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Solution

  • You don't wanna do that. And that won't work, as far as I know. You need to setup your constraints, either in your cell's init. Or in your cellForRow function.

    So in my codes below, I modified yours, and I add a property (height constraint) of your NestedView inside your TestCell cell. And I update that height inside the cellForRow.

    Edit: if you want to toggle a cell height, of course you do it the same way, you just need to reload the rows/sections or whole data of your tableView.

    ViewController.swift

    import UIKit
    import SnapKit
    
    class ViewController: UIViewController {
    
        let tableView = UITableView.init(frame: .zero)
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.addSubview(tableView)
            tableView.snp.makeConstraints { make in
                make.edges.equalTo(self.view)
            }
    
            tableView.register(UITableViewCell.self, forCellReuseIdentifier: "X")
            tableView.rowHeight = UITableView.automaticDimension
            tableView.estimatedRowHeight = 100
            tableView.delegate = self
            tableView.dataSource = self
        }
    
    }
    
    extension ViewController: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            if indexPath.row == 5 {
                let c = TestCell.init(style: .default, reuseIdentifier: nil)
                c.constraint_HeightOfNestedView?.update(offset: 100.0)
                return c
    
    
            }
    
            let c = tableView.dequeueReusableCell(withIdentifier: "X")
            c?.backgroundColor = .orange
            return c!
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 10
        }
    }
    

    TestCell.swift

    import SnapKit
    
    class TestCell: UITableViewCell {
        var constraint_HeightOfNestedView: Constraint?
        let nestedView = NestedView()
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    
            addSubview(nestedView)
            nestedView.snp.makeConstraints { make in
                make.edges.equalTo(self)
                self.constraint_HeightOfNestedView = make.height.equalTo(20.0).priority(999).constraint
            }
            backgroundColor = .blue
    
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    NestedView.swift

    import UIKit
    
    class NestedView: UIView {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            backgroundColor = .yellow
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }