简体   繁体   中英

Animate UIView with constraints after it was added programmatically

I have an UIView that I add programmatically to my UIViewController like this:

if let myView = Bundle.main.loadNibNamed("MyView", owner: self, options: nil)?.first as? MyView
    {
        self.view.addSubview(myView)
        myView.translatesAutoresizingMaskIntoConstraints = false
        //Horizontal orientation
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view":myView]))
    }

Thats fine, the view is added with the needed space on left and right at (0,0) of the view controller.

What I want to achieve is that the view animates in from the bottom of the view controller to the center, and later animates out from center to top. So first of all I tried to animate it to center from it's current position (0,0)

So what I did is:

UIView.animate(withDuration: 1.0,
                   animations:
    {
        let center = NSLayoutConstraint(item: myView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: CGFloat(0.0))
        self.view.addConstraint(center)
    })

Well the view is in the center but it is not animated. It directly jumps to it.

I searched and found a lot of answers that say I have to set the constant of the constraint and then call layoutIfNeeded() but all this examples use references to constraints that were set before as an IBOutlet, which I do not have as I add my view programmatically.

How can I animate the view properly, from bottom to center then center to top?

Thank you already!

Greetings

EDIT 1

Okay I'm going a bit further and explain what I want to achieve. On my view controller, after a 5 second timer that is displayed to the user, I want to show several questions, one after the other.

I want a question to slide in from bottom to center, been answered and then slide out from center to top. Then the next question comes in from bottom to center, is answered slides out and so on.

So after a 5 second timer I call method named slideInQuestion that creates a Question and animates it in:

func slideInQuestion()
{
    let question:QuestionView = createQuestion()
    UIView.animate(withDuration: 1.0,
                   animations:
    {
        self.yConstraint?.constant = CGFloat(0.0)
        self.view.layoutIfNeeded()
    })

The createQuestion method instantiates the question view and assigns the constraints to it

func createQuestion() -> QuestionView
{
    if let questionView = Bundle.main.loadNibNamed("QuestionView", owner: self, options: nil)?.first as? QuestionView
    {
        self.view.addSubview(questionView)
        questionView.translatesAutoresizingMaskIntoConstraints = false
        //Horizontal orientation
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view":questionView]))
        yConstraint = NSLayoutConstraint(item: questionView,
                                              attribute: .centerY,
                                              relatedBy: .equal,
                                              toItem: self.view,
                                              attribute: .centerY,
                                              multiplier: 1,
                                              constant: CGFloat(UIScreen.main.bounds.height))
        self.view.addConstraint(yConstraint!)
        return questionView
    }
    return QuestionView()
}

Before, I defined the yConstraint as an Instance variable as suggested by Duncan C like this:

    var yConstraint:NSLayoutConstraint?

So what I expect what I have right now is:

The question view is created, it's center is on the center of the view controller + the height of the screen. That makes it outside of the visible view on bottom of the screen. Then it slides within 1 second from this position to centerY (0.0) of the screen.

But what it does right now, is sliding from top of the screen to the centerY of the screen and also at the same time it resizes to the margins of left and right like it is set in the first constraint

self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view":questionView]))

I'm totally confused right now I hope my explanation helps and someone has an idea.

Thank you!

As I don't know how your View is created from your bundle, I've hacked together a quick example based of your code.

The key is that self.view.layoutIfNeeded() is called twice. Once after the view is added and the second time in the animation block. And the modification of the constraint is not within the animation block.

import UIKit

class ViewController: UIViewController {

    var yConstraint:NSLayoutConstraint?

    override func viewWillAppear(_ animated: Bool) {

        self.createView()

        self.view.layoutIfNeeded()

        self.yConstraint?.constant = 32
        UIView.animate(withDuration: 1.0) {
            self.view.layoutIfNeeded()
        }

    }

    func createView(){

        let questionView = UIView()
        questionView.backgroundColor = UIColor.red

        let heightConstraint = NSLayoutConstraint(item: questionView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.view.bounds.height-64)
        questionView.addConstraint(heightConstraint)

        self.view.addSubview(questionView)

        questionView.translatesAutoresizingMaskIntoConstraints = false

        //Horizontal orientation
        self.view.addConstraints(
            NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view":questionView]))

        yConstraint = NSLayoutConstraint(item: questionView,
                                         attribute: .top,
                                         relatedBy: .equal,
                                         toItem: self.view,
                                         attribute: .top,
                                         multiplier: 1,
                                         constant: CGFloat(UIScreen.main.bounds.height))
        self.view.addConstraint(yConstraint!)
    }
}

Create a constraint and save it in an instance variable. Then when you want to animate it, change the constant on the constraint and call layoutIfNeeded from the animation block just like you would for animating a storyboard-based constraint.

In your case, don't use let constraint = ... inside your function. Instead, use:

var centerConstraint: NSLayoutConstraint?

In viewWillAppear:

centerConstraint = NSLayoutConstraint(item: myView, 
  attribute: .centerY, 
  relatedBy: .equal, 
  toItem: self.view, 
  attribute: .centerY,  
  multiplier: 1,  
  constant: CGFloat(0.0))
self.view.addConstraint(center)

And when you want to animate:

UIView.animate(withDuration: 1.0,
                   animations:
    {
       centerConstraint.constant = newValue  //(Your value here.)
       myView.layoutIfNeeded()
    })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM