以编程方式添加 UIView 后,使用约束对其进行动画处理

Posted

技术标签:

【中文标题】以编程方式添加 UIView 后,使用约束对其进行动画处理【英文标题】:Animate UIView with constraints after it was added programmatically 【发布时间】:2017-02-24 20:09:24 【问题描述】:

我有一个 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 秒内从这个位置滑动到屏幕的 centerY (0.0)。

但它现在所做的是从屏幕顶部滑动到屏幕的 centerY,同时它调整到左右边缘的大小,就像在第一个约束中设置的那样

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

我现在完全糊涂了,希望我的解释有所帮助,并且有人有想法。

谢谢!

【问题讨论】:

为什么程序化与 IBOutlet 很重要?根据需要设置动画前约束,然后根据需要设置动画后约束,并在需要更改时在动画块内调用layoutIfNeeded 【参考方案1】:

由于我不知道您的视图是如何从您的包中创建的,因此我根据您的代码编写了一个快速示例。

关键是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 两次。将视图添加到控制器之后,然后在动画块中。今天学到了一些东西,谢谢!【参考方案2】:

创建一个约束并将其保存在实例变量中。然后,当您想要为其设置动画时,更改约束上的常量并从动画块中调用 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()
    )

【讨论】:

谢谢你,是的,你说得对,昨天肯定已经很晚了。事实上,我可以将约束存储在一个变量中。但我仍然有问题。我必须添加多个 cmets 否则我没有足够的字符来解释,对不起。 我把我的约束像 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!) 然后像 UIView.animate(withDuration: 1.0, animations: self.yConstraint?.constant = CGFloat(0.0) self.view.layoutIfNeeded() ) 一样更新它,然后,它从顶,为什么?不仅如此,它还根据我设置的第一个约束调整左右边框的大小(您可以在第一篇文章中看到它,即保持左右边距的那个)。对不起,我想的时间越长,我就越困惑。在 myView 上调用 layoutIfNeeded 而不是 self.view 没有效果,它只会左右调整大小 不要将代码放入 cmets。这是不可读的。编辑您的问题以在最后添加新代码,包括首先添加约束的方式和时间的详细信息。 并且不要在动画块中添加约束。您应该使用动画前约束设置您的视图,然后返回以便将视图添加到视图层次结构中并且可以应用动画前约束。同样,编辑您的问题以显示如何设置原始约束以及如何调用动画。

以上是关于以编程方式添加 UIView 后,使用约束对其进行动画处理的主要内容,如果未能解决你的问题,请参考以下文章

iOS以编程方式添加约束导致错误

如何以编程方式向以编程方式创建的 UIView 添加约束?

以编程方式向 UIView 添加约束

如何使用 Swift 以编程方式添加约束

以编程方式向 UIView 或 UIViewController 添加约束?

未能尝试在 SWIFT 中以编程方式在滚动视图中添加 UIView 约束