skip to Main Content

I’m adding a UIView to a view controller and want to it render and then immediately animate its movement, but a strange things is happening. The new view is being drawn at the destination where I want to animate it to and then animating to the origin that I had set it at. So it’s essentially happening in reverse. Here’s my code:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    @IBAction func buttonTapped(_ sender: Any) {
        let square = UIView()
        square.backgroundColor = .red
        square.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(square)
        [square.widthAnchor.constraint(equalToConstant: 100),
         square.heightAnchor.constraint(equalToConstant: 100),
         square.centerXAnchor.constraint(equalTo: view.centerXAnchor),
         square.centerYAnchor.constraint(equalTo: view.centerYAnchor)].forEach { $0.isActive = true }
        UIView.animate(withDuration: 2.0) {
            var frameCopy = square.frame
            frameCopy.origin.y -= 300
            square.frame = frameCopy
        }
    }

}

If I comment out the UIView.animate this is what gets created when I press the button, and this is what I expect

enter image description here

But then if I add the animation back in, this is what happens:

enter image description here

So as you can see the origin starts -300 off centre and then moves to the centre whereas I want to to start on centre and then move -300 off the screen. Can anyone help with this?

For the work I’m doing I need to add the view and then animate it immediately, I can’t add the view previously and animate it later.

2

Answers


  1. Chosen as BEST ANSWER

    I found out it's to do with the drawing cycle and if you change it to:

    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
        
        @IBAction func buttonTapped(_ sender: Any) {
            let square = UIView()
            square.backgroundColor = .red
            square.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(square)
            [square.widthAnchor.constraint(equalToConstant: 100),
             square.heightAnchor.constraint(equalToConstant: 100),
             square.centerXAnchor.constraint(equalTo: view.centerXAnchor),
             square.centerYAnchor.constraint(equalTo: view.centerYAnchor)].forEach { $0.isActive = true }
            UIView.animate(withDuration: 0.0) {
                square.layoutIfNeeded()
            } completion: { _ in
                UIView.animate(withDuration: 2.0) {
                    var frameCopy = square.frame
                    frameCopy.origin.y -= 300
                    square.frame = frameCopy
                }
            }
        }
    
    }
    

    Everything works as you'd expect


  2. You shouldn’t mix frame based layout and AutoLayout as they will fight each other and whatever you’re trying to do will be glitchy and not work well throughout iOS versions.

    What you want to do is change the constraint set (deactivate old and activate new) and run layoutIfNeeded on the changed view in an animation block to perform animations.

    Your case seems to be simpler and you can just capture your centerYAnchor constraint into a property and in the animation block do a constraint.constant = 300 or equivalent.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search