Search code examples
swiftuikituicollectionviewcellnslayoutconstraintuistackview

Prevent cell content from "jumping" when applying constraint


I have a subclassed UICollectionViewCell and I want it to expand when tapped.

To achieve this, I put the title into a view ("titleStack") and the body into a separate view ("bodyStack"), and then put both of them into a container UIStackView ("mainStack"). I then constrain the contentView of the cell to the leading, trailing, and top edges of mainStack.

When the cell is selected, a constraint is applied that sets the bottom of the contentView's constraint to be the bottom of bodyStack. When it's unselected, I remove that constraint and instead apply one that sets the contentView's bottom constraint equal to titleStack's bottom constraint.

For the most part this works well, but when deselecting, there's this little jump, as you can see in this video:

enter image description here

What I would like is for titleStack to stay pinned to the top while the cell animates the shrinking portion, but it appears to jump to the bottom, giving it a sort of glitchy look. I'm wondering how I can change this.

I've pasted the relevant code below:

private func setUp() {
    backgroundColor = .systemGray6
    clipsToBounds = true
    layer.cornerRadius = cornerRadius

    setUpMainStack()
    setUpConstraints()
    updateAppearance()
}

private func setUpMainStack() {
    
    contentView.constrain(mainStack, using: .edges, padding: 5, except: [.bottom])
    mainStack.add([titleStack, bodyStack])
    
    bodyStack.add([countryLabel, foundedLabel, codeLabel, nationalLabel])
}

private func setUpConstraints() {
    titleStack.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    
    closedConstraint =
        titleStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
    closedConstraint?.priority = .defaultLow // use low priority so stack stays pinned to top of cell
    
    openConstraint =
        bodyStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
    openConstraint?.priority = .defaultLow
}

/// Updates the views to reflect changes in selection
private func updateAppearance() {

    
    UIView.animate(withDuration: 0.3) {
        self.closedConstraint?.isActive = !self.isSelected
        self.openConstraint?.isActive = self.isSelected
    }
}

Thanks so much!


Solution

  • I was able to solve this by simply showing and hiding my "bodyStack" as well as using "layoutIfNeeded." I removed closedConstraint and openConstraint and just gave it a normal bottom constraint.

    The relevant code:

    func updateAppearance() {
    
            UIView.animate(withDuration: 0.3) {
                
                self.bodyStack.isHidden = !self.isSelected
                self.layoutIfNeeded()
            }
        }