How to update height Constraints by SnapKit and update cell height?
Asked Answered
S

5

10

I have a TableViewCell and two button to switch different constrain.
I want to update it's height constrain and cell height.
like following pic1
when I click buttonB, the view will change like pic2
Then I click buttonA, the view will back to pic1
I try to modify constrains, but I fail to update height.
Have any idea or answer to me?
Thanks

pic1

enter image description here

pic2

enter image description here

Here is code:

class CellContainView: UIView {

let buttonA: UIButton = { () -> UIButton in
    let ui = UIButton()
    ui.titleLabel?.numberOfLines = 0
    ui.setTitle("Click\nA", for: .normal)
    ui.backgroundColor = UIColor.blue
    return ui
}()

let buttonB: UIButton = { () -> UIButton in
    let ui = UIButton()
    ui.titleLabel?.numberOfLines = 0
    ui.setTitle("Click\nB", for: .normal)
    ui.backgroundColor = UIColor.gray
    return ui
}()

let buttonC: UIButton = { () -> UIButton in
    let ui = UIButton()
    ui.titleLabel?.numberOfLines = 0
    ui.setTitle("Click\nC", for: .normal)
    ui.backgroundColor = UIColor.brown
    return ui
}()

let labelA: UILabel = { () -> UILabel in
    let ui = UILabel()
    ui.text = "Test"
    return ui
}()

let viewA: UIView = { () -> UIView in
    let ui = UIView()
    ui.backgroundColor = UIColor.red
    return ui
}()

override init(frame: CGRect) {
    super.init(frame: frame)

    addUI()
    addConstrain()
}

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

func addUI() {

    self.addSubview(buttonA)
    self.addSubview(buttonB)
    self.addSubview(buttonC)
    self.addSubview(labelA)
    self.addSubview(viewA)

}

func addConstrain() {

    buttonA.snp.makeConstraints { (make) in
        make.left.top.equalToSuperview()
        make.height.equalTo(60)
    }

    buttonB.snp.makeConstraints { (make) in
        make.left.equalTo(buttonA.snp.right)
        make.top.right.equalToSuperview()
        make.width.equalTo(buttonA.snp.width)
        make.height.equalTo(buttonA.snp.height)
    }

    buttonC.snp.makeConstraints { (make) in
        make.left.equalTo(15)
        make.right.equalTo(-15)
        make.bottom.equalTo(-15)
        make.height.equalTo(50)
    }

    labelA.snp.makeConstraints { (make) in
        make.left.equalTo(15)
        make.top.equalTo(buttonA.snp.bottom).offset(15)
        make.width.equalTo(195)
        make.height.equalTo(50)
    }

    viewA.snp.makeConstraints { (make) in
        make.left.equalTo(labelA.snp.right)
        make.top.equalTo(buttonA.snp.bottom).offset(15)
        make.right.equalTo(-15)
        make.height.equalTo(50)
        make.bottom.equalTo(buttonC.snp.top).offset(-10)
    }
}

func updateConstrain(sender: UIButton) {

    switch sender {
    case buttonA:

        viewA.snp.updateConstraints { (make) in
            make.height.equalTo(50)
        }

    case buttonB:

        viewA.snp.updateConstraints { (make) in
            make.height.equalTo(150)
        }

    default:
        break
    }

}

}


class TestTableViewCell: UITableViewCell {

let cellContainView: CellContainView = { () -> CellContainView in
    let ui = CellContainView()
    ui.backgroundColor = UIColor.orange
    return ui
}()

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

    self.addSubview(cellContainView)

    cellContainView.snp.makeConstraints { (make) in
        make.left.top.equalTo(15)
        make.bottom.right.equalTo(-15)
    }

}

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

}


class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

let tableView: UITableView = { () -> UITableView in
    let ui = UITableView()
    return ui
}()

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.delegate = self
    tableView.dataSource = self
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 44
    tableView.register(TestTableViewCell.self, forCellReuseIdentifier: "TestTableViewCell")
    tableView.tableFooterView = UIView()

    self.view.addSubview(tableView)
    tableView.snp.makeConstraints { (make) in
        make.top.left.right.bottom.equalToSuperview()
    }
}

@objc func buttonAClicked(sender: UIButton) {

    let index = IndexPath(row: 0, section: 0)
    let cell = tableView.cellForRow(at: index) as! TestTableViewCell
    cell.cellContainView.updateConstrain(sender: sender)
    tableview.reloadData()
}

@objc func buttonBClicked(sender: UIButton) {

    let index = IndexPath(row: 0, section: 0)
    let cell = tableView.cellForRow(at: index) as! TestTableViewCell
    cell.cellContainView.updateConstrain(sender: sender)
    tableview.reloadData()
}

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell

    cell.cellContainView.buttonA.addTarget(self, action: #selector(buttonAClicked), for: .touchUpInside)
    cell.cellContainView.buttonB.addTarget(self, action: #selector(buttonBClicked), for: .touchUpInside)

    return cell
}

}

Update constrain problem when I click butttonB (Add update viewA heightConstrain & tableview.reloadData()):

TestCellUpdateHeight[29975:5195310] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
"<SnapKit.LayoutConstraint:[email protected]#75 UIButton:0x7fa1d0d0d680.height == 60.0>",
"<SnapKit.LayoutConstraint:[email protected]#89 UIButton:0x7fa1d0d10610.height == 50.0>",
"<SnapKit.LayoutConstraint:[email protected]#104 UIView:0x7fa1d0d11130.height == 150.0>",
"<SnapKit.LayoutConstraint:[email protected]#80 UIButton:0x7fa1d0d0f7b0.top == TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.top>",
"<SnapKit.LayoutConstraint:[email protected]#82 UIButton:0x7fa1d0d0f7b0.height == UIButton:0x7fa1d0d0d680.height>",
"<SnapKit.LayoutConstraint:[email protected]#88 UIButton:0x7fa1d0d10610.bottom == TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.bottom - 15.0>",
"<SnapKit.LayoutConstraint:[email protected]#102 UIView:0x7fa1d0d11130.top == UIButton:0x7fa1d0d0f7b0.bottom + 15.0>",
"<SnapKit.LayoutConstraint:[email protected]#105 UIView:0x7fa1d0d11130.bottom == UIButton:0x7fa1d0d10610.top - 10.0>",
"<SnapKit.LayoutConstraint:[email protected]#26 TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.top == TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000.top + 15.0>",
"<SnapKit.LayoutConstraint:[email protected]#27 TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.bottom == TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000.bottom - 15.0>",
"<NSLayoutConstraint:0x6080002819f0 'UIView-Encapsulated-Layout-Height' TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000'TestTableViewCell'.height == 230   (active)>"
)

Will attempt to recover by breaking constraint 

Soldo answered 26/10, 2018 at 10:58 Comment(0)
G
8

In general, to update the height constraint of a uiview:UIView you should save it first:

var viewHeightConstraint: Constraint!
uiview.snp.makeConstraints { (make) in
    viewHeightConstraint = make.height.equalTo(50).constraint
}

If you simply use updateConstraints function, it will add a new height constraint that causes the issue Unable to simultaneously satisfy constraints. So you have to remove the last one:

viewHeightConstraint.deactivate()

Then make constraint again:

uiview.snp.makeConstraints { (make) in
    viewHeightConstraint = make.height.equalTo(100).constraint
}
Groping answered 15/1, 2020 at 3:56 Comment(1)
u are setting variable value, not reference -> no need to keep track to viewHeightConstraintExcrescent
L
3

If you need recalculate height cell (without recreate cells / reload cells) just call:

tableView.beginUpdates()
tableView.endUpdates()
Longstanding answered 26/10, 2018 at 11:53 Comment(0)
A
2

What you currently do

labelA.snp.updateConstraints { (make) in
   make.bottom.equalTo(buttonC.snp.top).offset(-100)
}

will increase the bottom distance of the labelA , but will leave it's top constant the same you need to either

1- Update the height of the labelA

or

2- Update the height of the redView in front of it

Also don't forget to call

cell.cellContainView.updateConstrain(sender: sender)
cell.layoutIfNeeded()

note also because of cell reusing you may find other cell stretched , so you need to keep track of stretched cell indices and apply that inside cellForRowAt

You can also do this

func tableView(_ tableView: UITableView, 
     heightForRowAt indexPath: IndexPath) -> CGFloat
   return expandArr[indexPath.row] ? 300 : 100
}

where

var expandArr = [Bool]()

indicating whether the cell is expanded or not , also if you have a model you may add a bool value to it instead of this separate arr ( it's for illustration )

Adame answered 26/10, 2018 at 11:9 Comment(1)
Thanks. It's a nice advice. But I try and I also have the same constrain problem.Soldo
N
2

You cannot update cell's height just by changing its constraint.

Table can only update the height of its cells as a result of reloadData() call, after which it will ask its delegate for cell's height (or calculate automatically, if your cells are self-sizing).

So, to get the effect you want you might do something like the following.

In buttonBClicked method remember the state you want to get (using some variable maybe).

In table delegate's method tableView(_:, heightForRowAt:) return the correct height for the cell.

This is not ideal and serves just as a direction for considering your solution. My main point is that you need to reload table view to change cell's height.

Neapolitan answered 26/10, 2018 at 11:10 Comment(2)
Thanks. but I call the reloadData() and it shows me constrain problem.Soldo
Do you return updated height in tableView(_:, heightForRowAt:)?Neapolitan
E
1

i think you don’t need to change Bottom Constraint.what you required is Apply height Constraint to redView.and change it according to your button Selection.

func updateConstrain(sender: UIButton) {

    switch sender {
    case buttonA:

        redView.snp.updateConstraints { (make) in
            make.heigh.equalTo(10)
        }

    case buttonB:

        redView.snp.updateConstraints { (make) in
             make.heigh.equalTo(100)
        }

    default:
        break
    }

}
}

And don’t forget to reload Specific Row after updating Constraint

@objc func buttonAClicked(sender: UIButton) {

    let index = IndexPath(row: 0, section: 0)
    let cell = tableView.cellForRow(at: index) as! TestTableViewCell
    cell.cellContainView.updateConstrain(sender: sender)
    yourtableview.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.none)


}

@objc func buttonBClicked(sender: UIButton) {

    let index = IndexPath(row: 0, section: 0)
    let cell = tableView.cellForRow(at: index) as! TestTableViewCell
    cell.cellContainView.updateConstrain(sender: sender)
 yourtableview.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.none)
}
Excepting answered 26/10, 2018 at 12:5 Comment(1)
Sorry, I use this to update constrain, it seems have constrain problem.Soldo

© 2022 - 2024 — McMap. All rights reserved.