BostonAreaHuman
BostonAreaHuman

Reputation: 1461

Swift animation of constraint not working

The below function is just moving the view to a new place. It isn't showing the animation:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    UIView.animate(withDuration: 0.9, animations: {
        //self.leadingConst.constant -= 200

        self.view.setNeedsLayout()
    })
}

Upvotes: 3

Views: 3148

Answers (3)

Fattie
Fattie

Reputation: 12582

2023 answer

The correct solution was given in a comment, you need to call layoutIfNeeded()

A typical example

UIView.animate(withDuration: t) { [weak self] in
    guard let self = self else { return }
    self.bottom.constant = -H
    self.superview!.layoutIfNeeded()
}

IMPORTANT: "IN OR OUT" IS THE SAME

  1. somewhat bizarrely, it makes NO difference if you do:

this

UIView.animate(withDuration: t) {
    self.bottom.constant = -66
    self.superview!.layoutIfNeeded()
}

or this

self.bottom.constant = -66
UIView.animate(withDuration: t) {
    self.superview!.layoutIfNeeded()
}

There are 1000 totally incorrect comments on SO that you have to do it one way or the other to "mak eit work". It has nothing to do with the issue.

WHY THE SOLUTION IS layoutIfNeeded

It was perfectly explained in the @GetSwifty comment.

layoutIfNeeded is when the actual animation takes place. setNeedsLayout just tells the view it needs to layout, but doesn't actually do it.

WHY THE ISSUE IS CONFUSING: IT "SOMETIMES" WORKS ANYWAY

Often you actually do not need to explicitly have layoutIfNeeded. This causes lots of confusion. ie, this will work "sometimes" ...

UIView.animate(withDuration: t) {
    self.bottom.constant = -66
}

There are particular things to consider:

  • If you are animating "the view itself" (ie likely in a custom UIView subclass), the cycle can be different from when you are animating some view below you "from above"

  • If you have other animations going on, that can affect the view cycle.

Note that if it "magically works" without layoutIfNeeded, it may NOT work other times in your app, depending on what's going on in the app.

In short you must always add layoutIfNeeded, that's all there is to it.

Upvotes: 1

Alex Griswold
Alex Griswold

Reputation: 45

A few weeks ago I ran into the same problem. The problems came down to a few things. I was mixing adding views programmatically and adding them to the storyboard, I wasn't calling layoutSubviews(), and I was adding my constraints as IBOutlets as weak vars. So I would suggest to only use storyboard (otherwise animations start to get complicated quickly) and make your leadingConst constraint a var. You should also move the object that you want to animate in the animation block and then use a completion block to reset the constraint.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    view.layoutSubviews()
    UIView.animate(withDuration: 0.9, animations: {

        //something like this
        self.animatableObject.origin.x -= 200

    }, completion: {(finished: Bool) in

        self.leadingConst.constant -= 200

    })
}

I'm not sure what your trying to animate so the origin.x line is just an example.

Upvotes: 0

Luigi Taira
Luigi Taira

Reputation: 443

self.leadingConst.constant -= 200 should be outside UIView.animate, like this:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.leadingConst.constant -= 200
    UIView.animate(withDuration: 0.9, animations: {
        self.view.setNeedsLayout()
    })
}

reference: trying to animate a constraint in swift

Upvotes: 0

Related Questions