Tigran
Tigran

Reputation: 235

How animate UIView height constraint in Swift programmatically without making an IBOutlet?

I have created a button which is attached to the top, leading and trailing and has fixed height. And when I'm tapping the button the new UIView instance should appear with animation.

import UIKit

class ViewController: UIViewController {

var topButton = UIButton()
var myView = UIView()

override func viewDidLoad() {
    super.viewDidLoad()

    topButton = UIButton(type: .roundedRect)
    topButton.setTitle("Add Acctivity", for: .normal)
    topButton.backgroundColor = UIColor.purple
    topButton.setTitleColor(UIColor.white, for: .normal)
    topButton.titleLabel?.font = UIFont(name: "System", size: 26)
    topButton.translatesAutoresizingMaskIntoConstraints = false
    topButton.addTarget(self, action: #selector(expandMenu), for: .touchUpInside)
    view.addSubview(topButton)

    topButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
    topButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
    topButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
    topButton.heightAnchor.constraint(equalToConstant: 70).isActive = true

    myView.backgroundColor = UIColor.blue
    myView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(myView)
    myView.topAnchor.constraint(equalTo: view.topAnchor, constant: 70).isActive = true
    myView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
    myView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
    myView.heightAnchor.constraint(equalToConstant: 100).isActive = true

}

@objc func expandMenu() {

   myView.heightAnchor.constraint(equalToConstant: 300).isActive = true

    UIView.animate(withDuration: 1) {

        self.view.layoutIfNeeded()
    }
}



}

Nothing is working even I put the height to be 300

And the interesting thing is when I'm putting the height smaller than its initial value the animation works.

enter image description here

I saw many tutorials when people are creating IBOutlet from storyboard and then changing its constant value. But I'm wondering how to do that programmatically.

Upvotes: 2

Views: 5448

Answers (2)

Ahmad F
Ahmad F

Reputation: 31645

The issue in your code is that you are trying to reactivate another constraint that describes the height of myView, by saying (in expandMenu()):

myView.heightAnchor.constraint(equalToConstant: 300).isActive = true

I would assume that you are getting a runtime warning similar to:

[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. ( "NSLayoutConstraint:0x60400008e5b0 UIView:0x7fa16a419450.height == 100 (active)>", "NSLayoutConstraint:0x608000097e30 UIView:0x7fa16a419450.height == 300 (active)>" )

Will attempt to recover by breaking constraint

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.

means that myView already has a constraint for height, which is (in viewDidLoad()):

myView.heightAnchor.constraint(equalToConstant: 100).isActive = true

What you should do instead is to change the constant value of the same constraint instead. You could achieve it like this:

Declare a NSLayoutConstraint as instance variable:

var myViewHeightConstraint: NSLayoutConstraint!

and in viewDidLoad():

myViewHeightConstraint = NSLayoutConstraint(item: myView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 100)
myViewHeightConstraint.isActive = true

instead of:

myView.heightAnchor.constraint(equalToConstant: 100).isActive = true

Finally, in expandMenu():

myViewHeightConstraint.constant = 300

instead of:

myView.heightAnchor.constraint(equalToConstant: 300).isActive = true

Output:

enter image description here


Furthermore:

If you are aiming to let the same button to achieve the expand/collapse behavior, you would need to simply implement it as (in expandMenu()):

myViewHeightConstraint.constant = myViewHeightConstraint.constant == 300 ? 100 : 300

Upvotes: 7

Sweeper
Sweeper

Reputation: 271040

The constraint method returns an NSLayoutConstraint. You can assign it to a class level variable:

var heightConstraint: NSLayoutConstraint!

override func viewDidLoad() {
    // ...

    heightConstraint = myView.heightAnchor.constraint(equalToConstant: 100)
    heightConstraint.isActive = true
}

And then you will have a property heightConstraint that is very much like an IBOutlet. You can animate this the same way you animate an IBOutlet constraint.

Upvotes: 1

Related Questions