1

Objective: Apply a constraint based animation so that the UILabel changes height while maintaining its top anchor restraint.

Problem: The constraint and view update appropriately but there seems to be a timing discrepancy -- the height constraint changes first (as if from zero height), and not staying pinned to the top anchor constraint.

Thanks for any feedback or direction.

Example GIF.

class ViewController: UIViewController {

    private var button : UIButton = {
       let button = UIButton()
        button.backgroundColor = .red
        button.setTitle("Hello", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.textAlignment = .center
        button.titleLabel?.font = .systemFont(ofSize: 24, weight: .bold)
        button.setTitleColor(.white, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(changeConstraints), for: .touchUpInside)
        return button
    }()
    
    private var label : UILabel = {
       let label = UILabel()
        label.backgroundColor = .blue
        label.text = "This is my label"
        label.font = .systemFont(ofSize: 34, weight: .semibold)
        label.textAlignment = .center
        label.textColor = .white
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private var animationCount = 0
    
    private var labelHeightConstraint : NSLayoutConstraint = NSLayoutConstraint()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()

    }

    func setupUI() {
        
        view.addSubview(button)
        view.addSubview(label)

        // setting initial label height constraint
        labelHeightConstraint =  label.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6)
        labelHeightConstraint.isActive = true
        
       
        
        NSLayoutConstraint.activate([
        
        
            label.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
            label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),

            button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 20),
            button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
            button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
        

        ])
        
    }

    
    @objc func changeConstraints() {
        
        print("changeConstraints method CALLED")
        
        //count for animation state
        animationCount += 1
        
        UIView.animate(withDuration: 2.0) { [unowned self] in
            if animationCount % 2 == 0 {
                labelHeightConstraint.isActive = false
                labelHeightConstraint = label.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6)
                button.setTitle("GoodBye", for: .normal)
                button.backgroundColor = .purple
                button.setTitleColor(.white, for: .normal)
            } else {
                labelHeightConstraint.isActive = false
                labelHeightConstraint = label.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2)
                button.setTitle("Welcome Back", for: .normal)
                button.backgroundColor = .green
                button.setTitleColor(.black, for: .normal)
            }
            
            labelHeightConstraint.isActive = true
            self.view.layoutIfNeeded()
        }

        
    }
}

Marmaduk
  • 105
  • 6
  • Maybe start with [trying to animate a constraint in swift](https://stackoverflow.com/questions/25649926/trying-to-animate-a-constraint-in-swift) (this is my go to when I can't remember how to do it). You should modify the constraints before your start the animation and then simply trigger a layout pass within in the animation ... apparently – MadProgrammer May 15 '21 at 08:10
  • @MadProgrammer thanks for the link, it's useful but I'm still facing the same problem regarding the top anchor breaking to preserve the height constraint. I've gone about it multiple different ways but will work through the different options within that link and see if any produce the intended result. – Marmaduk May 15 '21 at 16:09

1 Answers1

0

Why don't you try with this code:

@objc func changeConstraints() {
    print("changeConstraints method CALLED")
    
    //count for animation state
    animationCount += 1
    if animationCount % 2 == 0 {
        labelHeightConstraint = label.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6)
        button.setTitle("GoodBye", for: .normal)
        button.setTitleColor(.white, for: .normal)
    } else {
        labelHeightConstraint = label.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2)
        button.setTitle("Welcome Back", for: .normal)
        button.setTitleColor(.black, for: .normal)
    }

    UIView.animate(withDuration: 2.0) {
        self.view.layoutIfNeeded()
        self.button.backgroundColor = self.animationCount % 2 == 0 ? .purple : .green
    }   
}

Activating and deactivating the constraint is not necessary. Assigning a new constraint is also not necessary, you can directly modify the multiplier.

Doing constraint changes inside the animations block is also not necessary, it will animate if you put layoutIfNeeded inside the block. Button title and titleColor are not animatable so I removed them from the block.

Gustavo Conde
  • 927
  • 12
  • 20