程序化添加 UIView 后使用约束动画

3

我有一个UIView,我通过编程方式将其添加到我的UIViewController中,就像这样:

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]))
    }

没问题,视图已经在视图控制器的(0,0)位置添加了所需的左右空间。

我想实现的是视图从视图控制器底部动画进入到中心,然后再从中心动画退出到顶部。

首先,我尝试将视图从当前位置(0,0)动画到中心。

所以我做的是:

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)
    })

视图位于中心,但没有动画效果,它直接跳转到该位置。

我搜索并找到了很多答案,说我必须设置约束的常量,然后调用layoutIfNeeded(),但所有这些示例都使用引用之前设置的约束作为IBOutlet,而我是通过编程方式添加我的视图。

如何正确地对视图进行动画处理,从底部到中心,然后从中心到顶部?

非常感谢!

问候

编辑1

好吧,我再进一步解释一下我想要实现什么。 在我的视图控制器上,在向用户显示5秒计时器后,我想逐个显示几个问题。

我希望一个问题从底部滑入中心,被回答后再从中心滑出到顶部。然后下一个问题从底部滑入中心,被回答后滑出等等。

因此,在5秒计时器之后,我调用名为slideInQuestion的方法创建一个问题并将其动画化:

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

createQuestion方法实例化问题视图并为其分配约束条件

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()
}

之前,我按照 Duncan C 的建议将 yConstraint 定义为实例变量:

    var yConstraint:NSLayoutConstraint?

我期望的结果是:

问题视图已创建,其中心位于视图控制器的中心+屏幕高度。这使得它在屏幕底部超出可见视图。然后它在1秒内从此位置滑动到屏幕中心点(0.0)。

但现在的情况是,它从屏幕顶部滑动到屏幕中心,并同时按照第一个约束设置的左右边距进行调整大小。

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

我现在完全困惑了,希望我的解释有助于理解并能给出一些想法。

谢谢!


为什么编程上的程序化和IBOutlet有关呢?您可以按任意方式设置预动画约束,然后按照您所需的方式设置后动画约束,并在需要更改这些约束时在动画块中调用layoutIfNeeded - Connor Neville
2个回答

4

我不知道您的 View 是如何从 bundle 创建的,因此基于您的代码,我组合了一个快速示例。

关键是要在动画块中两次调用 self.view.layoutIfNeeded()。一次是在添加视图之后,第二次是在动画块内。修改约束不在动画块内。

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!)
    }
}

谢谢!问题确实是我需要调用layoutIfNeeded两次。一次是在将视图添加到控制器后,另一次是在动画块中。 今天学到了东西,感谢! - Hardcore_Graverobber

1
创建一个约束并将其保存在实例变量中。然后,当您想要对其进行动画处理时,请更改约束上的常量,并从动画块中调用layoutIfNeeded,就像您为基于故事板的约束进行动画处理一样。
在您的情况下,请不要在函数内使用“let constraint = ...”。相反,请使用:
var centerConstraint: NSLayoutConstraint?

在视图即将显示的时候:
centerConstraint = NSLayoutConstraint(item: myView, 
  attribute: .centerY, 
  relatedBy: .equal, 
  toItem: self.view, 
  attribute: .centerY,  
  multiplier: 1,  
  constant: CGFloat(0.0))
self.view.addConstraint(center)

当您想要进行动画时:

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

谢谢,是的,你说得对,昨天可能已经太晚了。事实上,我可以将约束条件存储在变量中。但我仍然有问题。我必须添加多个注释,否则我没有足够的字符来解释,抱歉。 - Hardcore_Graverobber
我将我的约束条件放在了这里: yConstraint = NSLayoutConstraint(item: myView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: CGFloat(UIScreen.main.bounds.height)) self.view.addConstraint(yConstraint!) - Hardcore_Graverobber
并更新它,如下所示: UIView.animate(withDuration: 1.0, animations: { self.yConstraint?.constant = CGFloat(0.0) self.view.layoutIfNeeded() })但是,它从顶部滑入,为什么?而且不仅如此,它还调整了我设置的第一个约束的左右边框(您可以在第一个帖子中看到,它保持对左右的边距)。 很抱歉,我越想越困惑。 在myView上调用layoutIfNeeded没有效果,它只会从左侧和右侧调整大小。 - Hardcore_Graverobber
不要在注释中放置代码,这样会使其难以阅读。请编辑您的问题,在结尾处添加新代码,包括首次添加约束的详细信息。 - Duncan C
不要在动画块中添加限制条件。您应该使用预先设置好的约束条件来设置视图,然后返回到视图层次结构中添加视图,以便可以应用预先设置的约束条件。同样,编辑您的问题以显示如何设置原始约束条件以及如何调用动画。 - Duncan C
好的,抱歉我不知道那个。我编辑了我的问题,请告诉我现在是否更好。谢谢! - Hardcore_Graverobber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接